Monthly Archive

Categories

PowerShell Basics

Get-Date -UFormat

The Get-Date -UFormat parameter formats the date using Unix format. The full list of format specifiers are in the Notes section of the Get-Date help file.

Some examples are:

PS> Get-Date -UFormat '%Y-%m-%d %H:%M%:%S%Z'
2019-05-27 20:40:11+01

 

4 digit year – two digit month and day. Hour in 24 hour format and minutes and seconds and finally time zone as offset from GMT

 

Date in locale format

PS> Get-Date -UFormat %x
05/27/19

though it shows a US format for a UK locale?

 

PS> Get-Date -UFormat '%A %d %B %Y'
Monday 27 May 2019

day of week – day – Month (full name) 4 digit year.

 

Between the Format parameter and the UFormat parameter you should be able to get the date information into any format you require

Get-Date –Format

Get-Date –Format enables you to control the formatting of the datetime object returned by the cmdlet.

A standard call to get date returns:

PS> Get-Date

27 May 2019 12:36:47

 

The –Format parameter takes a value from the DateTimeFormatInfo class - https://docs.microsoft.com/en-us/dotnet/api/system.globalization.datetimeformatinfo?view=netframework-4.8 – to specify how you want the information to be formatted.

 

For instance:

Short and long forms of the date with no time

PS> Get-Date -Format d
27/05/2019
PS> Get-Date -Format D
27 May 2019

 

Full date and short or long time

PS> Get-Date -Format f
27 May 2019 12:48
PS> Get-Date -Format F
27 May 2019 12:48:38

 

No year or time

PS> Get-Date -Format m
27 May
PS> Get-Date -Format M
27 May

 

RFC1123 compliant

PS> Get-Date -Format R
Mon, 27 May 2019 12:50:07 GMT
PS> Get-Date -Format r
Mon, 27 May 2019 12:50:10 GMT

 

Sortable

PS> Get-Date -Format s
2019-05-27T12:50:45

 

Short and long time only

PS> Get-Date -Format t
12:51
PS> Get-Date -Format T
12:51:14

 

Sortable Universal time

PS> Get-Date -Format u
2019-05-27 12:51:55Z

Z refers to the time zone

 

Specify day month year format

PS> Get-Date -Format 'dd/MM/yyyy'
27/05/2019

Useful constants

PowerShell provides easy access to some useful constants. I often see people calculating these values rather than using the constants.

PowerShell recognises kb, mb, gb, tb and pb for kilobyte, megabyte, gigabyte, terabyte and petabyte respectively. You can use them like this:

PS> 1kb; 1mb; 1gb, 1tb, 1pb
1024
1048576
1073741824
1099511627776
1125899906842624

 

Don’t leave a space between the value and the constant.

You can use them in calculations:

PS> 7247437567256292 / 1gb
6749702.21450207
PS> 7247437567256292 / 1tb
6591.50606884968
PS> 7247437567256292 / 1pb
6.43701764536101

 

Fractional values are allowed:

PS> 27.457gb
29481729261.568

 

You can use upper case or lower case to denote the constant

PS> 27.457gb
29481729261.568
PS> 27.457GB
29481729261.568

 

Next time you need to work with megabytes or other common constants don’t calculate them use the constants in PowerShell

PowerShell for loop

PowerShell has a number of looping mechanisms – do-while; do-until; foreach; while and for loops. In this post I’ll show you how to use the PowerShell for loop.

A for loop iterates a predefined number 0f times. A basic for loop looks like this:

for ($i=0; $i -lt 10; $i++) {
$i
}

 

In the () you have 3 statements – they can be pipelines rather than simple assignments though most people just use assignments.

$i=0; - means set the counting variable $i to zero as the starting point

$i -lt 10; – means loop while the counting variable is less than 10

$i++ – means increment the counting variable by 1 after each loop

If you run the code you’ll see the numbers 0-9 displayed.

$i is traditionally used as the loop counting variable but it doesn’t have to be $i. It can be any arbitrary variable

for ($somevar=0; $somevar -lt 10; $somevar++) {
$somevar
}

 

The reason for using $i is traditional. It traces back to one of the early programming languages – Fortran – in which variables starting with i,j,k,l,m or n were integers by default. The variable i became the counter variable because of its position in the alphabet. The tradition has progressed down through programming languages since that day.

The loop count can be decremented

for ($i=0; $i -gt -10; $i--) {
$i
}

This loop will output 0 to –9

 

If you want to break out of a for loop use break

for ($i=0; $i -lt 10; $i++) {
if ($i -eq 5){break}
$i
}
Write-Host "I now equals $i"

which outputs

0
1
2
3
4
I now equals 5

 

Alternatively, to force the loop to skip further processing and move to the next iteration

for ($i=0; $i -lt 10; $i++) {
if ($i -eq 5){continue}
$i
}

which outputs 0-4 then 6-9

 

The for loop is a basic loop that’s best used when you want to iterate over a set of code a number of times.

You can use variables when assigning the start and endpoints

$x = 1
$y = 10
for ($i=$x; $i -lt $y; $i++) {
$i
}

 

Also, the loop counter doesn’t have to change by 1 each time

for ($i=0; $i -lt 10; $i+=2) {
$i
}

outputs 0,2,4,6,8

PowerShell file extensions

There are a number of file extensions associated with PowerShell. If you’re not aware of them they may cause you problems. You’ll commonly find these PowerShell file extensions:

 

.ps1 – a PowerShell script. May contain functionality such as functions or workflows. This is the most common extension

 

.psm1 – a PowerShell module file. Contains one or more advanced functions that are managed as a package

 

.psd1 – a PowerShell data file. Most commonly used as module manifest to control the .psm1 files that are loaded and the functionality that is exposed

 

.ps1xml – a PowerShell XML file. Used by the formatting and type systems e.g. Registry.format.ps1xml or  types.ps1xml.

 

.pssc – a PowerShell configuration file. Created by New-PSSessionConfigurationFile when you want to define a constrained or restricted endpoint

 

.psrc – a PowerShell role capability file. Used during the creation of a JEA endpoint

 

.cdxml – a cmdlet definition XML file. Used by the PowerShell cmdlets-over-objects engine to publish a module created from a CIM class e.g. the NetAdapter module

PowerShell pause

PowerShell pause – how can you pause a PowerShell script?

Two ways come to mind.

First if you just want the script to pause for a specified time period then you can use Start-Sleep

1..10 |
foreach {
  $PSItem
  if ($PSItem -eq 5) {
    Write-Warning -Message "Starting sleep"
    Start-Sleep -Seconds 5
  }
}

Run this and you’ll see the numbers 1-5 output then then warning message. After the delay you’ll see the numbers 6-10 output.

But what if you want to control the pause manually? Not sure if there are advantages to this approach but if you do need to do this you can use Read-Host

1..10 |
foreach {
  $PSItem
  if ($PSItem -eq 5) {
    Read-Host -Prompt "Press Enter key to continue"
  }
}

You’ll see the numbers 1-5 output then the message

Press Enter key to continue:

After pressing the enter key the script continues and outputs 6-10

Don’t know why you’d want to do this in an automation scenario but the technique is there if you need it – I don’t recommend the approach.

There are also a few cmdlets that can be used under specific circumstances:

Wait-Event
Wait-Job
Wait-Process
Wait-VM

Also check the –wait parameter on Restart-Computer

PowerShell for loop

Loops are a construction seen in most scripting and programming languages. A loop is used to repeat a set of statements a set number of times or until a specific criterion is met or while a specific criterion is true. In this post I’ll describe the PowerShell for loop.

For loops are found in many languages. A for loop is sometimes referred to as a counting loop as it will have a counter that starts at a pre-set value and counts up to a specific value. The counter is usually incremented by 1 for each iteration of the loop.

A PowerShell for loop looks like this

for ($i=1; $i -le 10; $i++){$i}

The counter – $i – is initialised to 1. The loop will execute while $i is less than or equal to 10 and $i is incremented by 1 for each turn round the loop. In this case the loop lists the value of the counter.  You can see the results like this

PS> $results = for ($i=1; $i -le 10; $i++){$i}
PS> "$results"
1 2 3 4 5 6 7 8 9 10

You can also run loops where the counter decreases

PS> $results = for ($i=10; $i -ge 1; $i--){$i}
PS> "$results"
10 9 8 7 6 5 4 3 2 1

A for loop is great when you need to perform the loop and exact number of times but if your loop depends on a specific criterion you’re better off using a while loop or a do loop which I’ll cover in another post

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.

Modifying AD users in bulk

Modifying AD users in bulk involves either setting one or more properties to the same value for a set of users or reading in the values you need from a data source of some kind.

We prepared some test data in the last post so lets see how we use it.

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
Get-ADUser -Identity $user.Id |
Set-ADUser -Division $user.Division -EmployeeNumber $user.EmployeeNumber
}

The simplest way is to read in the data and store as a collection of objects. Use foreach to iterate through the set of user information. Get-ADUser gets the appropriate AD account which is piped to Set-ADUser. Set-ADUser is a great cmdlet because it has parameters for most of the user properties.

In this case though we know that some of the users don’t have employee numbers. This means a bit more work. Two approaches are possible – use splatting and the parameters used above or use the –Replace option

Lets look at splatting first

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
$params = @{
Division = $user.Division
EmployeeNumber = 0
}

if ($user.EmployeeNumber) {
$params.EmployeeNumber = $user.EmployeeNumber
}
else {
$params.Remove('EmployeeNumber')
}

Get-ADUser -Identity $user.Id |
Set-ADUser @params
}

As before read the user information into the $users variable. Iterate over the users with foreach. Create a hashtable for the parameters and their values. Division is always present so that can be set directly. Employeenumber should be tested and if  present the place holder value should be overwritten with the correct value otherwise Employeenumber is removed from the hashtable.

The user account is found and Set-ADUser sets the correct values. Notice how the hashtable is specified to the cmdlet.

Splatting is a great way to dynamically set the parameters you’re using on a particular cmdlet.

Set-ADUser has an alternative – the –Replace parameter.

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
$params = @{
division = $user.Division
employeeNumber = 0
}

if ($user.EmployeeNumber) {
$params.EmployeeNumber = $user.EmployeeNumber
}
else {
$params.Remove('EmployeeNumber')
}

Get-ADUser -Identity $user.Id |
Set-ADUser -Replace $params
}

This is very similar to the splatting example but instead of splatting the hashtable you use it as the value input to the Replace parameter. If you wrote  out the command it would look like this:

Set-ADUser –Replace @{division = ‘Division B’; employeeNumber  = 100}

With –Replace you’re using the LDAP names of the properties rather than the GUI or PowerShell name – there are differences for instance surname is sn in LDAP.

Modifying AD users in bulk is straightforward with PowerShell and its relatively easy to deal with missing values if you adopt one of the above ideas. Splatting is probably the easiest in this case.