Monthly Archive

Categories

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.

Linking disks, partitions and logical drives

A question of the forums was asking about discovering disk information. They were trying to pipe the output of Get-WmiObject into another Get-WmiObject. that won’t work. There is another way. On Windows machines physical drives are divided into 1 or more partitions which are each divided into one or more logical disks. Linking disks, partitions and logical drives is a relatively simple process.

You can start at the physical disk and work down to the logical disks or start at the logical disk and work back to the physical disk. Lets start with the logical disk.

$diskinfo = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" | 
foreach { 
  $props = $null 
  
  $part = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition 
  $disk = Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_DiskDrive 
  
  $props = [ordered]@{ 
     Disk = $disk.Index 
     Model = $disk.Model 
     Firmware = $disk.FirmwareRevision 
     SerialNUmber = $disk.SerialNumber 
     'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2) 
     Partitions = $disk.Partitions 
     Partition = $part.index 
     BootPartition = $part.BootPartition 
     'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
     Blocks = $part.NumberOfBlocks 
     BlockSize = $part.BlockSize 
     LDiskName = $psitem.Caption 
     FileSystem = $psitem.FileSystem 
      LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
     LDiskFree =  [math]::Round(($psitem.FreeSpace / 1GB ), 2) 
  }

  New-Object -TypeName PSObject -Property $props

}

$diskinfo

Use Get-CimInstance to retrieve the instances of the Win32_LogicalDisk class. Use a filter for DriveType = 3 – which is local disks (as far as the server is concerned – they could be on a SAN or NAS).

Foreach of the disks get the associated partition and use that object to get the associated physical drive.

CIM (WMI) has the concept of associators and references.

A reference is a pointer showing you which instance is associated with another instance. For example:

PS> Get-CimInstance -ClassName Win32_LogicalDiskToPartition


Antecedent      : Win32_DiskPartition (DeviceID = "Disk #0, Partition #1") 
Dependent       : Win32_LogicalDisk (DeviceID = "C:") 
EndingAddress   : 511578663935 
StartingAddress : 368050176 
PSComputerName  :

Logical disk C: is associated with partition #1 on disk #0

If you want to actually get the associated class then you do this

PS> $ld = Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"' 
 PS> Get-CimAssociatedInstance -InputObject $ld -ResultClass Win32_DiskPartition

Name             NumberOfBlocks       BootPartition        PrimaryPartition     Size                Index 
 ----             --------------       -------------        ----------------     ----                ----- 
 Disk #0, Part... 998458230            False                True                 511210613760        1

or

PS> Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"' |   Get-CimAssociatedInstance -ResultClass Win32_DiskPartition

Name             NumberOfBlocks       BootPartition        PrimaryPartition     Size                Index 
 ----             --------------       -------------        ----------------     ----                ----- 
 Disk #0, Part... 998458230            False                True                 511210613760        1

Once you’ve go the partition and physical disk instances. Populate your output object and loop. Notice that the pipeline is output directly to the variable $diskinfo. You don’t need to build arrays – get the pipeline to do it for you.

Each logical disk gets an output like this

Disk              : 0 
 Model             : Samsung SSD 840 PRO Series 
 Firmware          : DXM06B0Q 
SerialNUmber      : S1AXNSAF329511V 
DiskSize(GB)      : 476.93 
 Partitions        : 3 
 Partition         : 1 
BootPartition     : False 
PartitionSize(GB) : 476.1 
 Blocks            : 998458230 
BlockSize         : 512 
LDiskName         : C: 
FileSystem        : NTFS 
LDiskSize         : 476.1 
LDiskFree         : 212.33

That’s working up the stack. What about working down. That’s a similar process:

$diskinfo = Get-CimInstance -ClassName Win32_DiskDrive | 
foreach { 
  $disk = $psitem 
  
  $parts = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition

  foreach ($part in $parts) { 
    
    Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_LogicalDisk | 
    foreach { 
       $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Index 
        Model = $disk.Model 
        Firmware = $disk.FirmwareRevision 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2) 
        Partitions = $disk.Partitions 
        Partition = $part.index 
        BootPartition = $part.BootPartition 
         'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
        Blocks = $part.NumberOfBlocks 
        BlockSize = $part.BlockSize 
        LDiskName = $psitem.Caption 
        FileSystem = $psitem.FileSystem 
        LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.FreeSpace / 1GB ), 2) 
      }

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

Start with getting the instances of Win32_Diskdrive. Foreach instance get the associated partitions - Win32_DiskPartition.

Iterate through the partitions and get the associated logical disk. Create your object and output.

NOTE: neither of these techniques will show the partitions that don’t contain logical drives so you won’t see the boot partition and other “hidden partitions” on modern Windows machines. if you need those look at Win32_DiskPartition directly.

Office holidays

Office holidays are a great thing. They usually occur on public holidays. There’s a web site - www.officeholidays.com – you can use to discover the public holidays in your country. 133 countries are available - http://www.officeholidays.com/countries/index.php.

You can also use PowerShell to extract the information

$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 " " 
  $y = $x[0] -split "day" 
  
  $props = [ordered]@{ 
    DayOfWeek = "$($y[0])day" 
    Day = $x[1] 
    Month = $y[1] 
    Holiday = $x[4..($x.Count-1)] -join " " 
  } 
  
  New-Object -TypeName PSObject -Property $props 
  
 }

$hols | Format-Table -AutoSize –Wrap

Create a string to hold the uri of the country you want. You may need to use the website to discover how they format your country name. Use Invoke-WebRequest to get the html containing the holidays. You then need to get the inner text of the html.

DayDateHolidayComments 
SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday 
MondayJanuary 02 Jan 2 Day after New Years Day Scotland Only 
MondayJanuary 02 Jan 2 New Years Day (observed) Except Scotland 
TuesdayJanuary 03 Jan 3 New Year's Day (in lieu) Scotland Only 
TuesdayFebruary 28 Feb 28 Pancake Tuesday Shrove Tuesday. Not a Public Holiday 
WednesdayMarch 01 Mar 1 St Davids Day Wales Only. Not a public holiday 
FridayMarch 17 Mar 17 St Patricks Day Northern Ireland Only 
SundayMarch 26 Mar 26 Mothering Sunday Not a National Holiday 
FridayApril 14 Apr 14 Good Friday Friday before Easter Sunday 
MondayApril 17 Apr 17 Easter Monday Except Scotland 
SundayApril 23 Apr 23 St George's Day England Only. Not a public holiday 
MondayMay 01 May 1 Early May Bank Holiday First Monday in May 
MondayMay 29 May 29 Spring Bank Holiday Last Monday in May 
SundayJune 18 Jun 18 Father's Day 3rd Sunday in June. Not a public holiday 
WednesdayJuly 12 Jul 12 Battle of the Boyne Northern Ireland Only 
MondayAugust 07 Aug 7 August Bank Holiday Scotland Only. First Monday in August 
MondayAugust 28 Aug 28 August Bank Holiday Last Monday in August (except Scotland) 
SundayNovember 05 Nov 5 Guy Fawkes Night England Only. Not a public holiday 
SundayNovember 12 Nov 12 Remembrance Sunday Not a public holiday. Sunday closest to 11 November 
ThursdayNovember 30 Nov 30 St Andrews Day Scotland Only. If November 30 falls on a weekend, the next Monday is a bank ho liday instead 
MondayDecember 25 Dec 25 Christmas Day 
TuesdayDecember 26 Dec 26 Boxing Day

Its a single string but does contain new line characters so you can split it into an array.

Iterate through the array – skip the first line – and extract the day of the week, the day, month and holiday details. Create an object for output. Display the objects to see your holidays.

DayOfWeek Day Month    Holiday                                                 
 --------- --- -----    -------                                                 
 Sunday    01  January  New Years Day First Monday if 1st is Saturday or Sunday 
 Monday    02  January  Day after New Years Day Scotland Only                   
 Monday    02  January  New Years Day (observed) Except Scotland                
 Tuesday   03  January  New Year's Day (in lieu) Scotland Only                  
 Tuesday   28  February Pancake Tuesday Shrove Tuesday. Not a Public Holiday    
 Wednesday 01  March    St Davids Day Wales Only. Not a public holiday          
 Friday    17  March    St Patricks Day Northern Ireland Only                   
 Sunday    26  March    Mothering Sunday Not a National Holiday                 
 Friday    14  April    Good Friday Friday before Easter Sunday                 
 Monday    17  April    Easter Monday Except Scotland                           
 Sunday    23  April    St George's Day England Only. Not a public holiday      
 Monday    01  May      Early May Bank Holiday First Monday in May              
 Monday    29  May      Spring Bank Holiday Last Monday in May                  
 Sunday    18  June     Father's Day 3rd Sunday in June. Not a public holiday   
 Wednesday 12  July     Battle of the Boyne Northern Ireland Only               
 Monday    07  August   August Bank Holiday Scotland Only. First Monday in 
                        August                                                  
 Monday    28  August   August Bank Holiday Last Monday in August (except       
                       Scotland)                                               
 Sunday    05  November Guy Fawkes Night England Only. Not a public holiday     
 Sunday    12  November Remembrance Sunday Not a public holiday. Sunday closest 
                       to 11 November                                          
 Thursday  30  November St Andrews Day Scotland Only. If November 30 falls on a 
                       weekend, the next Monday is a bank holiday instead      
 Monday    25  December Christmas Day                                           
 Tuesday   26  December Boxing Day

I’ve used some rather ugly, brute force string handling. There should be a better way to extracting the data from the string but I need to think about it

Variable as a where clause

A post on the forum about using a variable as a where clause looked interesting. What the user wanted to do was to define a variable that contained the filter to be used by Where-Object.

As an example consider filtering the output of Get-Service to display only services that are stopped

PS> Get-Service | where Status -eq "Stopped"

You have to revert to the old style syntax

PS> Get-Service | where {$_.Status -eq "Stopped"}

The answer then becomes to create a scriptblock for the filter

PS> $filter = {$_.Status -eq "Stopped"} 
 PS> Get-Service | where $filter

You could then use the filters like this

function gs { 
  param ( 
  [string]$status 
  )

switch ($status){ 
   "Running" {$filter = {$_.Status -eq "Running"}; break} 
   "Stopped" {$filter = {$_.Status -eq "Stopped"}; break} 
   default   {$filter = {$_.Status -like "*"}; break} 
 }

Get-Service | where $filter
}

PS> gs -status Running
PS> gs -status stopped
PS> gs -status *

If you need a variable as a where clause then use a scriptblock.

File name starting with space

Interesting question on the forum regarding finding files with a file name starting with a space.

First problem was creating some files to match the criteria. Renaming in file explorer didn’t work so back to PowerShell

PS> Rename-Item -Path C:\test\file1.txt -NewName "C:\test\ file1.txt"

PS> Rename-Item -Path C:\test\junk.txt -NewName "C:\test\ junk.txt"

Make sure you put the new name in quotes so the space is included as part of the file name

After that its actually quite easy

PS> Get-ChildItem -Path C:\test\ -Filter ' *.txt'


    Directory: C:\test


Mode                LastWriteTime         Length Name 
 ----                -------------         ------ ---- 
 -a----       30/06/2017     15:38             16  file1.txt 
-a----       30/06/2017     10:50             26  junk.txt

Use the Filter parameter and specify ' *.txt' as the pattern you want to match

Topics for PowerShell Summit 2018

The planning for Summit 2018 has started – to be honest it started before Summit 2017 opened. We’ve reached the stage where we need to start thinking about the broad topics for PowerShell Summit 2018.

What do you want to hear about? Not the session titles, content and speakers but the broad areas of content you want us to include. We can’t actually promise to cover everything requested because we’re dependent on whats submitted when we open our call for topics towards the end of the month.

Looking at the agenda for Summit 2017 we had these very broad groups

PowerShell tool making
DSC and DSC resources
PowerShell Github repository
PowerShell v6
Remoting
Testing - Pester
Azure
PowerShell Functions
JEA
PowerShell v6
PowerShell on Linux
PowerShell modules
Regular Expessions
MSDeploy
PKI
Powershell Jobs, Workflows and runspaces
Nano server
PowerShell cmdlets - compiled and script

Are there any we should drop? Is there a topic we should include – this far out we can commission a specific expert speaker to cover a topic if required. This is your opportunity to help shape Summit 2018. Let us know what you think

MVP 2017

I received the email this afternoon stating I’d received an MVP award for 2017-2018 for my work with PowerShell. This is the 10th consecutive year I’ve been honoured with an MVP award. Its as big an honour to receive the 10th as it was to receive the first.