Thursday, July 14, 2011

Setup Windows Remote Management and PSSession

Executing remote commands in Powershell 1 required a good deal of knowledge of powershell and the use of SysInternals' PsExec tool. It had it's drawbacks and inherent insecurites. Perhaps you wanted to remote deploy a SharePoint 2007 package from a TFSBuild script. If the Build Service account doesn't have access to the server or to deploy to SharePoint, psexec needs to have credentials in the command. It works, but not secure. Depending on how much access you have to the build environment, it might be the only option. Using PsExec to deploy packages to SharePoint 2010 throws errors and all attempts thus far have failed. Below is an example adapted from Lee Holmes:
$expression = "C:\SharePointDeploy\Deploy.ps1";
$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
$encodedCommand = [Convert]::ToBase64String($commandBytes)
psexec /acceptEula /username domain\SPServiceAcct /password s0meP@ssw0rd \\server cmd /c "echo . | powershell -EncodedCommand $encodedCommand"
cmd /c pause
We can use the Windows Remote Management (WinRM) to enable a better, faster remoting experience. Both computers must be set up to allow WinRM. First set up the "remote" server:
Enable-PSRemoting -force
Enable-WSManCredSSP –role Server -force
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1000
Before we get the client setup we need to setup the client computer, WinRM cannot be setup when connected to public networks or have network adapters set to public category. Continue after the below script if this doesn't apply. If this is the case, those networks need to be changed to not public. I HIGHLY recommend not being connected to untrusted networks when setting this up.
$nlm = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"));
  $connections = $nlm.getnetworkconnections();
  $connections | % {
      Write-Host "Connection " $_.getnetwork().getcategory();
      if ($_.getnetwork().getcategory() -eq 0)
      {
          Write-Host "Setting connection to private.";
          $_.getnetwork().setcategory(1);
      }
  }
It is time to setup the local computer which will be sending the commands to the remote computer. A security decision must be made as to which computers the local computer needs access to. Pass the computer(s) in the arguments.
param (
  $machines = $(throw "machines is required.")  # i.e. "*.domain.com" OR "name1.domain.com, name2.domain.com" sans quotes
)
Enable-PSRemoting -force
Enable-WSManCredSSP –role Client –DelegateComputer $machines -force
Now that the environments are setup, the following can be used to connect to the remote server.
Enter-PSSession -ComputerName name.domain.com
#Run your commands
If you need to specify which user to connect as or use CredSSP, you can use the following (this is needed when you want to get into SharePoint 2010):
Enter-PSSession -ComputerName name.domain.com -Authentication CredSSP -Credential $([Security.Principal.WindowsIdentity]::GetCurrent().Name)
#Run your commands
Finally, to clean up. To exit out of the session:
Exit-PSSession
In a future blog post I will cover using New-SPSession.

I adapted some of the Windows Remote Management (WinRM) parts of Zach Rosenfield's Remote Install of SharePoint (with SPModule) post for the purposes of this post. I probably could have found it in a million different places, but that was the site I found the information on.

Thursday, June 23, 2011

Programmatically Install X509 Certificates And Set Permissions

Using X509 certificates to secure WCF services doesn't take long to setup in a Windows 2008 R2 environment, but is rather user intensive and provides numerous opportunities for error. Below is a script that installs a certificate to the Personal store of the local computer and grants full access to the specified users. This saves time deploying across multiple environments and streamlines the setup for users who don't have experience with certificates.
param
(
	[switch] $Verbose
)

if ($Verbose)
{
    $VerbosePreference = 'Continue'
}

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Security")
$StoreScope = "LocalMachine";
$StoreName = "My";
if (Test-Path "cert:\$StoreScope\$StoreName")
{
	$certfile = Get-Item ".\ServiceX509.pfx";
	$certfile.FullName;

	$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certfile.FullName,$null;

	try
	{
		$store = New-Object System.Security.Cryptography.X509Certificates.X509Store $StoreName, $StoreScope;
		$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
		$store.Add($cert)
		$store.Close()
		Write-Verbose "Successfully added '$certfile' to 'cert:\$StoreScope\$StoreName'."
		$keyPath = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
		$folderlocation = gc env:ALLUSERSPROFILE 
		$folderlocation = $folderlocation + "\Microsoft\Crypto\RSA\MachineKeys\"  
		$filelocation = $folderlocation + $keyPath 
		("NETWORK SERVICE", "domain\AppPoolSvc") | % { 
			icacls $filelocation /grant "$($_):(F)";
			Write-Verbose "Successfully granted '$_' to '$($cert.FriendlyName)'.";
		}
	}
	catch
	{
		Write-Error ("Error adding '$certfile' to 'cert:\$StoreScope\$StoreName': $_ .") -ErrorAction:Continue
	}
}

Wednesday, April 13, 2011

Powershell Base64 Image Generator For Data URIs

Data URIs are gaining in popularity. There are websites that will generate the URI (like http://www.motobit.com/util/base64-decoder-encoder.asp), but requires the uploading of files to someone else server, corporate policies can make these unusable. I haven't seen one in Powershell, so I made one. The code is pretty basic.

You need to know what MIME type for the file, but they are pretty easy to discover. The most common ones I have used:
  • image/gif
  • image/x-icon
  • image/vnd.microsoft.icon
  • image/png
  • image/jpeg

#####
#
#	Base64.ps1
#		Converts a file to or from base 64
#
#	Useful for generating data protocol image addresses:
#	In CSS:
#		background: #ff url('data:image/gif;base64,<Base64String>')
#
#	In HTML:
#		<img src="data:image/gif;base64,<Base64String>" alt="X">
#
#	Usage (encode):
#		.\base64.ps1 favicon.ico favicon.b64 -encode
#
#	Usage (encode & copy to clipboard):
#		.\base64.ps1 favicon.ico -encode -SetClipboard
#
#	Usage (decode):
#		.\base64.ps1 favicon.b64 favicon.ico -decode
#
#
#####
param (
	[string] $source = $(Throw 'source: required parameter'),
	[string] $destination = "",
	[switch] $encode,
	[switch] $decode,
	[switch] $SetClipboard
)

function Main
{
	$file = Get-Item($source);
	if ($encode)
	{
		$bytes = get-content -encoding byte $file.Fullname
		$result = [System.Convert]::ToBase64String($bytes);
		if ($SetClipboard) {Set-Clipboard $result;}
		if ($destination.length -eq 0)
		{
			return $result;
		}
		else
		{
			set-content -encoding UTF8 -Path ".\$destination" -Value $result;
		}
	}
	elseif ($decode)
	{
		$bytes = get-content -encoding UTF8 $file.Fullname;
		[System.Convert]::FromBase64String($bytes) | set-content -encoding Byte -Path ".\$destination";
	}
	else
	{
		Write-Host("The encode or decode switch is required.");
	}
}

. Main

For more information on Data URIs, refer to RFC2397 or the Wiki page for the Data URI Scheme for examples on how to use them.

Saturday, October 30, 2010

Connection was Unexpectedly Closed

I encountered an issue were a web service was calling a mainframe web service and every other time I would get the following error.

Error: "The underlying connection was closed: A connection that was expected to be kept alive was closed by the server."

As it turned out the mainframe was not setup to keep connections alive. Due to limitations with the mainframe, changing this functionality was not an option. So a simple partial class handled this automatically.

namespace <webservicenamespace> 
{
  public partial class <webserviceproxy> 
  {
    protected override System.Net.WebRequest GetWebRequest(Uri uri) {
      System.Net.HttpWebRequest webRequest = base.GetWebRequest(uri);

      webRequest.KeepAlive = false;

      return webRequest;
    }
  }
}

Saturday, October 2, 2010

Handling Enumerations from a Web Service with Generic Functions

Looking back at some code I ran across a handy piece of code that I wrote to save a good deal of typing, increase maintainability and readability.

Dealing with enumerations from a web service can be confusing at first. The enumeration field names are the same on both sides (client and server) of the web service, however the values are likely not going to be the same. This is especially true if the enumeration is defined with specific values that don't start with 1. The client side will give the enumeration values starting with 1. This means that when saving the value back to a web service object with a property of the enumeration type, you must make sure that the correct enumeration value is returned.

We run into situations where the enumeration value needs to be resolved from enumeration field name and the enumeration field name needs to be resolved from a value. The to just handle this inline can be rather hard to read and requires duplicate typing. Generic functions to the rescue.

Public Shared Function ParseEnum(Of T)(ByVal value As String) As T
    Return CType(System.Enum.Parse(GetType(T), value, True), T)
End Function


Public Function ResolveEnumToValue(Of T)(ByVal value As Object) As String
    Dim genericType As Type = GetType(T)
    Return [Enum].GetName(genericType, value)
End Function

Thursday, June 17, 2010

Using SQL Transactions in Powershell

It occurred to me that I have not seen a write up on System.Transactions.TransactionScope usage in powershell. I have tried this on Powershell v2, not sure if this works in Powershell 1. So here it goes...

The example below just shows the usage, I leave it as an exercise to the reader to add in the database query code.

"Start. " + [DateTime]::Now().ToString();

try {
 $transScope = New-Object System.Transactions.TransactionScope;

 ## Insert Queries

 "Complete";
 $transScope.Complete();
}
catch [Exception] {
    #$_ | fl * -Force
 Write-Host $_.Exception.ToString();
}
finally {
 if ($transScope) {
  $transScope.Dispose();
 }
}

"Done. " + [DateTime]::Now().ToString();

A more complex example just waiting for SQL statements. It shows some nested transactions.
"Start. " + [DateTime]::Now().ToString();

try {
 $transScope = New-Object System.Transactions.TransactionScope;
 
 ## Insert Queries
 
 try {
  ## Performs queries that are not included in a transaction.
  $transScope2 = New-Object System.Transactions.TransactionScope([System.Transactions.TransactionScopeOption]::Suppress);
  
  ## Insert Queries
  
  $transScope2.Complete();
 }
 finally {
  if ($transScope2) {
   $transScope2.Dispose();
  }
 }
 
 ## Insert Queries

 try {
  ## Perform actions that should be included in a side transaction outside of the enclosing transaction
  $transScope3 = New-Object System.Transactions.TransactionScope([System.Transactions.TransactionScopeOption]::RequiresNew);
  
  ## Insert Queries
  
  $transScope3.Complete();
 }
 finally {
  if ($transScope3) {
   $transScope3.Dispose();
  }
 }
 
 ## Insert Queries
 
 "Complete";
 $transScope.Complete();
}
catch [Exception] {
    #$_ | fl * -Force
 Write-Host $_.Exception.ToString();
}
finally {
 if ($transScope) {
  $transScope.Dispose();
 }
}

"Done. " + [DateTime]::Now().ToString();