Tag Archives: Powershell

Returning a collection of objects from a PowerShell function

If you’re writing a function that returns a collection then don’t forget to include the comma operator in the return statement. If you forget it your function will work correctly when the collection contains multiple objects, but fails when it contains 1 object.

Take the following buggy example:

function GiveMeAllTheThings()
{
    $myarray = @()
    #fill $myarray with results of type String. Assume that
    #run-time conditions determine if it is filled with 
    #0, 1 or more items and that each item is a string
    return $myarray;
}
$result = GiveMeAllTheThings
$result.GetType().FullName

If you execute this code when $myarray has many strings in it, the returned type from the function is System.Object[]. If $myarray has only 1 string in it, then the returned type will be System.String.

The code should have been written like this:

function GiveMeAllTheThings()
{
    ...
    return ,$myarray;
}

Starting a process in PowerShell with dynamic command-line parameters

Starting a command-line process from PowerShell is very easy. A simple java -jar helloworld.jar works just fine. However when I’m starting nodes in my Selenium Grid I need to dynamically create a different number of parameters. The following code will fail because the various strings wont be correctly mapped to the usual argv[] input parameters for the java.exe process:

$arguments = ''
$arguments += ' ' + '-jar xxxx.jar'
$arguments += ' ' + '-browser'
$arguments += ' ' + 'browserName=firefox,version=3.6,platform=WINDOWS'
$arguments += ' ' + '-browser'
$arguments += ' ' + 'browserName=internet explorer,version=10,platform=WINDOWS'
java $arguments

Instead, just create an array of command-line arguments like this (assume that $Capabilities is an array of hash tables)

        $arguments = @()
        $arguments += '-jar'
        $arguments += $Jar
        $arguments += '-role'
        $arguments += 'node'
        $arguments += '-port'
        $arguments += 5555
        $arguments += '-hub'
        $arguments += '"' + 'http://127.0.0.1:4444/grid/register' + '"'

        foreach ($hashTable in $Capabilities)
        {
            $arguments += '-browser'
            $strCaps = ($hashTable.GetEnumerator() | ForEach-Object { '$($_.Key)=$($_.Value)' }) -join ','
            $strCaps = '"' + $strCaps + '"'
            $arguments += $strCaps
        }
        java $arguments

Creating site collections in their own content database

Central Admin doesn’t allow you to choose in which Content Database to create a new Site Collection. With PowerShell its easy:

New-SPSite  `
    –ContentDatabase "WSS_Content_SecondDB" `
    -Url "http://portal.contoso.com/sites/MyNewSiteCollection" `
    -Template "STS#0"  
    -Name "SitecCollectionTest" `
    –Description "A Site Collection created in a separate Content Database"  `
    -OwnerAlias "CONTOSO\Gerben" `
    –OwnerEmail "gerben@contoso.com" `

Backing up Azure VMs with PowerShell

When experimenting in my lab environment I want to create a backup of the virtual machines. The following PowerShell script will do just that. I assume you’ve already setup your PowerShell to work with azure by doing the following:

  1. Setup the Azure PowerShell cmdlets (see: http://azure.microsoft.com/en-us/downloads/)
  2. imported your Publish Settings File (see Get-AzurePublishSettingsFile and Import-AzurePublishSettingsFile)
  3. Defined which storageaccount to use with Set-AzureStorageAccount
  4. Shutdown all the Virtual Machines
Import-Module Azure -ErrorAction Stop
$backupContainerName = "backups"
function Backup-Lab
{
    $vms = Get-AzureVM

    if (! (Get-AzureStorageContainer -Name  $backupContainerName -ErrorAction SilentlyContinue) )
    {
        New-AzureStorageContainer -Name $backupContainerName -Permission Off
    }

    foreach ($vm in $vms)
    {
        Write-Host "backing up machine: " $vm.Name
        $disks = @()
        $disks +=  $vm | Get-AzureOSDisk
        $disks +=  $vm | Get-AzureDataDisk

        foreach($disk in $disks)
        {
            $DiskBlobName = $disk.MediaLink.Segments[-1]
            $DiskContainerName = $disk.MediaLink.Segments[-2].Split('/')[0]
            Write-Host "disk: " $disk.DiskName
            #Start an asynchronous copy of the VHD to our backup destination
            Start-AzureStorageBlobCopy -SrcContainer $DiskContainerName -SrcBlob $DiskBlobName -DestContainer $backupContainerName -DestBlob $DiskBlobName
            #Wait for the copy to complete
            Get-AzureStorageBlobCopyState -Blob $DiskBlobName -Container $DiskContainerName -WaitForComplete
        }
    }

Your new SharePoint site isn’t accesible from the server, but it works from other machines.

You’ve just installed and configured a SharePoint server and you fire-up IE on the server to see if everything works…..Oops, IE keeps asking for your credentials and eventually returns a HTTP 401. Then you notice that the sites are accessible from other machines. In this case you’re running into a security measure called the loopback check security feature. See http://support.microsoft.com/kb/896861 for the details.

Here’s how to disable the check just for your specific sites on that machine with a short PowerShell script:

$sites=@(
    "portal.contoso.com",
    "my.contoso.com"
)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0" -PropertyType MultiString -Name BackConnectionHostNames -Value $sites

Here is how to completely disable the Loopback check on Windows using a single PowerShell command:

New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name DisableLoopbackCheck -Value 1 -PropertyType DWORD

grepping in Powershell

Many unix/linux users are intimately familiar with regular expressions and using them with grep, sed or awk in a pipeline. A typical usage scenario is the following command:

ls | grep -i '.*[0-9].*\.dll' | sort 

So how do we do grep in PowerShell? Well, we can use PowerShell’s operators:

Operator Description
-match Matches its input property agains a regular expression. By default Powershell uses case-insensitive matching. Thats different than .Net’s default behavior with the RegEx class
-imatch Case-insensitive version of Match
-cmatch Case-sensitive version of Match
-like This compares its input against a string that can contain a wildcard. So no regular expressions usable here
-contains Only tells you if the input does or doesn’t contain the exact value you supplied

For our specific example, the PowerShell equivalent is:

Get-ChildItem | where { $_.Name -match ".*[0-9].*\.dll" } | Sort-Object -Property Name

And gives us the following output:

    Directory: C:\Windows\System32

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        20-11-2010     22:29     640512 advapi32.dll
-a---         11-6-2011      3:58     138056 atl100.dll
-a---         14-7-2009      3:14      65024 avicap32.dll
-a---        20-11-2010     22:29      91648 avifil32.dll
-a---         14-7-2009      3:14      10752 bitsprx2.dll
...
...

Another approach is to directly use the RegEx class from .Net. An example:

$matchOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
$matcher = new-object System.Text.RegularExpressions.Regex (".*[0-9].*\.dll",$matchOptions)
$files  = Get-ChildItem
$files | where { $matcher.IsMatch($_.Name)  }

PowerShell for monitoring proces memory usage

Here is a small bit of PowerShell that monitors the memory usage of a process and outputs the results in a CSV format in the locale of your choosing. I needed this because the sysadmins disabled perfmon and I really wanted to import the raw data into Excel to produce some graphs.

You can use it as follows

PS> Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
PS> Monitor-Process -Interval 30 -Locale 'nl-NL' -ProcessName winword | Tee-Object -Append -FilePath C:\Temp\test.txt
-OR-
PS> Monitor-Process -Interval 30 -Locale 'nl-NL' -ProcessId 5800 | Tee-Object -Append -FilePath C:\Temp\test.txt

function Format-MemoryAsCSV
{
  [CmdletBinding(SupportsShouldProcess=$false)]   
  param  
  (   
    [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="Proces to get/format memory from")]    
    [Alias("Process")]    [ValidateNotNull()]    
    $InputObject,
    
    [Parameter(Mandatory=$false, HelpMessage="Which locale to use when formatting numbers (en-US, nl-NL")]
    $Locale = [System.Threading.Thread]::CurrentThread.CurrentCulture.Name 
  )
   process 
   {
            $localeObject = new-object CultureInfo($Locale)
            $Separator = $localeObject.TextInfo.ListSeparator
            $item = Get-Item $InputObject -ErrorAction SilentlyContinue
            $PrivateMemorySize64 = [math]::round($_.PrivateMemorySize64/ 1MB,1).ToString($localeObject ) #Total amount of memory allocated to proces. I.e.. RAM + pagefile. Excludes sharedlibs
            $VirtualMemorySize64 = [math]::round($_.VirtualMemorySize64/ 1MB,1).ToString($localeObject ) #total virtual address space of the process
            $WorkingSet64        = [math]::round($_.WorkingSet64/ 1MB,1).ToString($localeObject ) #Amount of memory thats held in physical RAM chips
            Write-Output "$([DateTime]::Now.ToString($localeObject))$($Separator)$($_.Id)$($Separator)$($_.Name)$($Separator)$($PrivateMemorySize64)$($Separator)$($VirtualMemorySize64)$($Separator)$($WorkingSet64)" 
     }
}

function Monitor-Process
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param  
    ( 
    [Parameter(Mandatory=$false, HelpMessage="NUmber of seconds between samples")]     
    $Interval  = 30,

    [Parameter(Mandatory=$false, HelpMessage="Which locale to use when formatting numbers (en-US, nl-NL")]
    $Locale = [System.Threading.Thread]::CurrentThread.CurrentCulture.Name, 
  

    [Parameter(parametersetname="ByName")]
    [Parameter( Mandatory=$false, HelpMessage="Name of the process(es) to monitor")]
    $ProcessName,

    [Parameter(parametersetname="ByPID")]
    [Parameter(Mandatory=$false, HelpMessage="PID of the process to monitor")]
    $ProcessId
    
    )

    process
    {
        do
        {
            switch ($PsCmdlet.ParameterSetName)
            {
                "ByName" {  Get-Process -Name $ProcessName -ErrorAction SilentlyContinue  | Format-MemoryAsCSV -Locale $Locale }
                "ByPID"  {  Get-Process -Id   $ProcessId   -ErrorAction SilentlyContinue  | Format-MemoryAsCSV -Locale $Locale }
            }

            [System.Threading.Thread]::Sleep(1000 * $Interval)
        } while ($true)
    }
}

PowerShell for recursive file and directory listings with MD5 hashes

The other day I needed to know if the files in a specific directory structure on different systems were identical or different. Many tools exist for doing file and directory comparisons but the challenge here was that each system was locked-down, no installation of software was possible. And even worse, we needed to compare at least 3 different machines that couldn’t reach each others drives or shares. Instead of hunting around to find which one of the many tools would work in this environment I thought that Windows Powershell should be able to do this quite easily. In fact, it turned out to be so easy that just finding another tool would have taken much longer.

The script uses the Get-ChildItem cmdlet to recursively get each file/directory in the form of a System.IO.FileInfo or System.IO.DirectoryInfo object. We calculate the MD5 hash using the ComputeHash() method of the System.Security.Cryptography.MD5CryptoServiceProvider class. We simply write the hash and some interesting fields as a CSV string to the output.

Here’s the bits of code glued into a single script.

function Get-MD5
{
  [CmdletBinding(SupportsShouldProcess=$false)]
  param
  (
    [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="file(s) to create hash for")]
    [Alias("File", "Path", "PSPath", "String")]
    [ValidateNotNull()]
    $InputObject
  )
 
  begin
  {
     $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider
  }
 
  process
  {
    $hashBytes = ''

    $item = Get-Item $InputObject -ErrorAction SilentlyContinue
 
    if ($item)
    {
      $InputObject = $item
    }
 
    if ($InputObject -is [System.IO.FileInfo])
    {
      $stream    = $null;
      $hashBytes = $null
 
      try
      {
        $stream     = $InputObject.OpenRead();
        $hashBytes  = $hasher.ComputeHash($stream);
      }
      finally
      {
        if ($stream -ne $null)
        {
          $stream.Close();
        }
      }
    }

 
    Write-Output ([BitConverter]::ToString($hashBytes)).Replace("-",'')
  }
}

function Get-FilesWithHash
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param
    (
        [string]$Directory,
        [switch]$Recurse
    )
 

    Get-ChildItem -Path $Directory -Recurse:$Recurse | foreach {
        $item = $_
        $hash = ''
        if (!$_.PSIsContainer) 
        {
            $hash = Get-MD5 $_    
        }
        else
        {
            $hash = ''
        }
        Write-Output "$($item.FullName),$hash,$($item.Length),$($item.CreationTime),$($item.LastWriteTime)" 
        
    }
}
Logo of Techdays 2014

Techdays 2014 – Desired State Configuration (DSC) with PowerShell

Presented by Jeff Wouters at Techdays 2014. Using PowerShell we can define what configuration we want and the DSC framework will make sure that this configuration is enforced on a multitude of machines. Very much like Chef and Puppet.

Some examples of configuration settings you can define are Installing windows features, MSI’s, registry settings, services that must be started/stopped etc etc.

Jeff’s tips were:
You need WMF v4.

Install KB2883200. This is a must-have, don’t even try to use DSC without it.

Take a look at TechNet for overview and details.

Want to see what resources you have to use for DSC? Run the Get-DSCResource cmdlet.

Take a look at PowerShell DSC community resources.