Monthly Archive

Categories

Create a directory

PowerShell enables you to work with the file system on your machine – one question that often comes up is how to create a directory.

When working interactively you can use md

PS> md c:\testf1


    Directory: C:\


 Mode                LastWriteTime         Length Name 
 ----                -------------         ------ ---- 
 d-----       19/08/2017     14:24                testf1

md doesn’t look like a PowerShell command – more like an old style DOS command.

Its actually an alias for mkdir

PS> Get-Command md

CommandType     Name                                               Version    Source 
 -----------     ----                                               -------    ------ 
 Alias           md –> mkdir

Which raises the question – what’s mkdir?

PS> Get-Command mkdir

CommandType     Name                                               Version    Source 
 -----------     ----                                               -------    ------ 
 Function        mkdir

Its a function that PowerShell creates for you

Digging into the function

PS> Get-ChildItem -Path function:\mkdir | select  -ExpandProperty  Definition

<# 
 .FORWARDHELPTARGETNAME New-Item 
 .FORWARDHELPCATEGORY Cmdlet 
 #>

[CmdletBinding(DefaultParameterSetName='pathSet', 
    SupportsShouldProcess=$true, 
    SupportsTransactions=$true, 
    ConfirmImpact='Medium')] 
    [OutputType([System.IO.DirectoryInfo])] 
param( 
    [Parameter(ParameterSetName='nameSet', Position=0, ValueFromPipelineByPropertyName=$true)] 
    [Parameter(ParameterSetName='pathSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] 
    [System.String[]] 
    ${Path},

    [Parameter(ParameterSetName='nameSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] 
    [AllowNull()] 
    [AllowEmptyString()] 
    [System.String] 
    ${Name},

    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] 
    [System.Object] 
    ${Value},

    [Switch] 
    ${Force},

    [Parameter(ValueFromPipelineByPropertyName=$true)] 
    [System.Management.Automation.PSCredential] 
    ${Credential} 
 )

begin {

    try { 
        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-Item', [System.Management.Automation.CommandTypes] 
 ::Cmdlet) 
        $scriptCmd = {& $wrappedCmd -Type Directory @PSBoundParameters } 
        $steppablePipeline = $scriptCmd.GetSteppablePipeline() 
        $steppablePipeline.Begin($PSCmdlet) 
    } catch { 
        throw 
    }

}

shows that its based on New-Item

PS> New-Item -Path c:\ -Name testf2 -ItemType Directory


    Directory: C:\


 Mode                LastWriteTime         Length Name 
 ----                -------------         ------ ---- 
 d-----       19/08/2017     14:32                testf2

The default for New-Item in the filesystem is to create a file so you need to use –ItemType Directory to create the folder.

If the folder you’re creating is a subfolder of a non-existent folder you can create the hierarchy is one go

PS> New-Item -Path c:\ -Name testf3\tests1 -ItemType Directory


    Directory: C:\testf3


 Mode                LastWriteTime         Length Name 
 ----                -------------         ------ ---- 
 d-----       19/08/2017     14:33                tests1


 PS> Get-ChildItem -Path c:\testf3 -Recurse


    Directory: C:\testf3


 Mode                LastWriteTime         Length Name 
 ----                -------------         ------ ---- 
 d-----       19/08/2017     14:33                tests1

This can get complicated if you try to nest too many levels so I recommend explicitly creating each level of your folder hierarchy. Its much easier to maintain and modify

PowerShell foreach

PowerShell has a number of ways to perform a loop – I recently counted seven distinct methods. If you can’t list them all don’t worry one is very esoteric and unexpected. I’ll enumerate them in a future post. For now I want to concentrate on a source of confusion – especially to newcomers to PowerShell – namely the PowerShell foreach statements.

The confusion arises because there are effectively two foreach statements. One is a PowerShell keyword that initiates a loop and the other is an alias for  a cmdlet.

Lets start with the foreach loop.

$numbers = 1..10
foreach ($number in $numbers){
  [math]::Pow($number, 2)
}

foreach in this case is used to iterate over a collection. In the example above $numbers is an array of numbers 1 to 10. Foreach number in the array it is raised to the power 2 – squared.

Remember that PowerShell is unique among shells in that you can use pipelines in many places that other languages insist on variables so you could change the example to

foreach ($number in 1..10){
  [math]::Pow($number, 2)
}

The array is generated and then iterated over as earlier.

If you see foreach as the first command on a line you’re dealing with the foreach keyword and therefore a loop.

On the other hand if you see foreach in a pipeline

1..10 | foreach {
  [math]::Pow($_, 2)
}

or

1..10 | foreach {
  [math]::Pow($psitem, 2)
}

you’re dealing with the cmdlet. $_ or $psitem denote the object currently on the pipeline. foreach is an alias for the Foreach-Object cmdlet and you’re using –Process as a position parameter for the scriptblock. Written in full you’re doing this

1..10 | ForEach-Object -process {
  [math]::Pow($_, 2)
}

or

1..10 | ForEach-Object -process {
  [math]::Pow($psitem, 2)
}

Just to add to the confusion you also have the option to use the foreach method on the collection

(1..10).foreach({[math]::Pow($psitem, 2)})

This isn’t seen as much though it should be remembered as this is the fastest way to iterate over a collection.

In summary

foreach starting a line is the looping keyword. Faster than the pipeline but increases memory overheads as  the collection has to pre-generated

foreach on the pipeline is an alias for foreach-object. Lower memory requirements as the collection is passed down the pipeline but a bit slower

().foreach({}) is a method on the collection (we treat it as an operator in PowerShell in Action) and is fast but in terms of coding style may be more intuitive to developers than admins.

Get-ADUser filtering

Saw a question on the forums that revolved around Get-ADUser filtering.

Initial code was like this

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like $_.Name}
}

which on the face of it seems reasonable. However, you get errors like this

Get-ADUser : Property: 'Name' not found in object of type:
'System.Management.Automation.PSCustomObject'.
At line:3 char:3
+   Get-ADUser -Filter {Name -like $_.Name}
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ADUser], ArgumentException
    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft
   .ActiveDirectory.Management.Commands.GetADUser

Change –like to –eq and you’ll still get the error.

This won’t work either:

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like $($_.Name)}
}

You get messages about path errors.

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like "$($_.Name)"}
}

will run but won’t return any data.

This will run and return the correct data.

Import-Csv .\users.txt |
foreach {
  $name = $_.Name
  Get-ADUser -Filter {Name -like $name}
}

Alternatively, you can use quotes so that the filter is a string

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter "Name -like '$($_.Name)'"
}

Another option is to use the LDAP filter syntax

Import-Csv .\users.txt |
foreach {
  $name = $_.Name
  Get-ADUser -LDAPFilter "(Name=$name)"
}

Import-Csv .\users.txt |
foreach {
  Get-ADUser -LDAPFilter "(Name=$($_.Name))"
}

The help file about_activedirectory_filter is well worth reading. It doesn’t seem to be installed with the AD module on Windows Server 2016. You can read it on line at https://technet.microsoft.com/en-us/library/hh531527(v=ws.10).aspx

You’ll also see links to

about_ActiveDirectory  - overview of module

about_ActiveDirectory_Identity

about_ActiveDirectory_ObjectModel

Get-ADUser filtering isn’t the most obvious of topics to get your head round but these examples should help you make your filters work. If you’re building a complex filter build it up a step at a time so you can test each stage.

PowerShell Summit 2018: Call for topics

The call for topics for the PowerShell and DevOps Summit 2018 is now open - https://powershell.org/2017/08/01/76318/

We’re looking for sessions (45 or 105 minute) that span the whole range of PowerShell usage and knowledge PLUS sessions on DevOps  practices.

This is your opportunity to speak at the premier PowerShell event of 2018.

PowerShell documentation

The home of Microsoft’s PowerShell documentation is changing from MSDN to https://docs.microsoft.com

The PowerShell documentation is currently opened sourced at https://github.com/powershell/powershell-docs

This change makes accessing the documentation easier

You have to laugh

Sometimes things just happen and you have to laugh.

So I decided I wanted to get back to working with the Windows 10 Insider previews (and Windows Server previews). This time I decided to use VMs rather than my working machine so that interruptions were minimised.

I created a new Windows 10 VM and as normal for VMs I set the initial memory to 512MB and used dynamic memory so that the machine could claim more RAM if required. Windows 10 installed with no problems. (Remember this).

I then went into Window Update and signed into the Windows Insider program. After triggering a scan fro updates build 16241 showed up and started downloading. Great.

It tried to install but failed with a message that 2GB of RAM was needed to perform the install!

So I can install from scratch with less than 2GB of RAM but I can’t update the build unless I have 2GB RAM.

Nice piece of joined up thinking there guys.

Sometimes you just have to laugh.

Unblock and rename files

I have a bunch of files that I’ve downloaded to a specific folder. I need to unblock and rename files in that folder. The rename involves replacing a plus sign with a space.

$path = 'C:\Users\Richard\Downloads\Walks'

$files = Get-ChildItem -Path $path -File

foreach ($file in $files) { 
  Unblock-File -Path $file.FullName 
  $newname = $file.FullName -replace '\+', ' ' 
  Rename-Item -Path $file.FullName -NewName $newname 
 }

Get-ChildItem -Path $path

I initially tried using a single pipeline but Unblock-File doesn’t generate any output which also blocks the pipeline – oops.

Read the list of files into an array. Iterate over the array and Unblock each file. Then rename the file. To use the –replace operator you need to escape the plus sign.

Display the files post rename as a check.

Change a computer’s description

The Win32_OperatingSystem class exposes the machines Description. This is how you can easily change a computer’s description.

PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description 
-----------

PS> Get-CimInstance -ClassName Win32_OperatingSystem | Set-CimInstance -Property @{Description = 'Richards Laptop'} 
PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description 
----------- 
Richards Laptop

You can see that the description is originally blank. Get the CimInstance of Win32_OperatingSystem and pipe it to Set-CimInstance. The property to change and its new value are held in the hash table that’s the value given to the –Property parameter. You can modify multiple properties at once – just add them as property-value pairs to the hash table

Control split output

In this post I’ll show you show to control split output – that is control the number of strings that are returned.

If you use –split with just a delimiter you’ll get a split occurring at every occurrence of the delimiter:

PS> 'SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday' -split ' ' 
SundayJanuary 
 01 
 Jan 
 1 
 New 
Years 
 Day 
 First 
 Monday 
 if 
 1st 
 is 
 Saturday 
 or 
 Sunday

But we want the holiday information to be in a single string. Rather than spending effort putting it back together you can control the number of strings that are output:

PS> 'SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday' -split ' ',5 
SundayJanuary 
 01 
 Jan 
 1 
New Years Day First Monday if 1st is Saturday or Sunday

In this case we’ve said we want 5 strings returned so everything after the 4th split is returned as a single string.

This makes our coding easier and neater

$uri = "http://www.officeholidays.com/countries/united_kingdom/index.php" 
 $html = Invoke-WebRequest -Uri $uri 
 $holidays = ($html.ParsedHtml.getElementsByTagName("table") | 
 where ClassName -eq 'list-table' | 
 select -ExpandProperty InnerText) -split "`n"

$holidays.Count

$hols = foreach ($holiday in $holidays[1..($holidays.Count -1)]){ 
  $x = $holiday -split ' ',5 
  $y = $x[0] -split "day" 
  
  $props = [ordered]@{ 
    DayOfWeek = "$($y[0])day" 
    Day = $x[1] 
    Month = $y[1] 
    Holiday = $x[4] 
  } 
  
  New-Object -TypeName PSObject -Property $props 
  
 }

$hols | Format-Table -AutoSize -Wrap

When I wrote this:

https://richardspowershellblog.wordpress.com/2017/07/09/office-holidays/

I said that the string handling was ugly and there must be a better way – I remembered!

More diskinfo

Yesterday I showed how to get the disk, partition and logical disk information using CIM. Today I want to show more diskinfo techniques.

This time we’ll use the Storage module which was introduced with Windows 8. Underneath the covers it uses CIM – just different classes. The storage module doesn’t differentiate between volumes  and logical disks – it just uses volumes.

To start at the physical disk and get the partition and volumes:

$diskinfo = Get-Disk | foreach {

  $parts = Get-Partition -DiskNumber $psitem.DiskNumber | where DriveLetter

  $disk = $psitem

  foreach ($part in $parts) { 
    
    Get-Volume -Partition $part | 
    foreach { 
      $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Number 
         Model = $disk.Model 
        Firmware = $disk.FirmwareVersion 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2) 
        Partitions = $disk.NumberOfPartitions 
        Partition = $part.PartitionNumber 
        BootPartition = $part.IsBoot 
        'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
        VolumeBlockSize = $psitem.AllocationUnitSize 
        LDiskName = $psitem.DriveLetter 
        FileSystem = $psitem.FileSystem 
         LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.SizeRemaining / 1GB ), 2) 
       }

      New-Object -TypeName PSObject -Property $props 
    } 
  } 
 } 
 $diskinfo

And to go the other way

$diskinfo =  Get-Volume | 
 where {$_.DriveLetter -AND $_.DriveType -eq 'Fixed'} | 
foreach {

      $part = Get-Partition -DriveLetter $psitem.DriveLetter 
       
      $disk = Get-Disk -Partition $part

      $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Number 
         Model = $disk.Model 
        Firmware = $disk.FirmwareVersion 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2) 
        Partitions = $disk.NumberOfPartitions 
        Partition = $part.PartitionNumber 
        BootPartition = $part.IsBoot 
        'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
         VolumeBlockSize = $psitem.AllocationUnitSize 
        LDiskName = $psitem.DriveLetter 
         FileSystem = $psitem.FileSystem 
        LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.SizeRemaining / 1GB ), 2) 
      }

      New-Object -TypeName PSObject -Property $props 
 } 
 $diskinfo

The number of blocks doesn’t seem to be available – suppose you could calculate it – otherwise the information is the same as with the CIM classes you saw last time. Some of the property names are different.