Monthly Archive

Categories

PowerShell Basics

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.

Test data for bulk AD changes

I’ve had a number of questions about changing AD user data in bulk. If you need to do that you need some test data. The specific questions were around setting the Division property and the EmployeeNumber at the same time – but some accounts didn’t have an employee number.

First you need to generate some test data

$count =  1

Get-ADUser -Filter * -SearchBase 'OU=UserAccounts,DC=Manticore,DC=org' |
foreach {

$props = @{
Id = $psitem.SamAccountName
Division = ''
EmployeeNumber = $count
}
switch ($count % 3){
0 {$props.Division = 'Division A'}
1 {$props.Division = 'Division B'}
2 {$props.Division = 'Division C'}
}

if (-not ($count % 15)){$props.Remove('EmployeeNumber')}

New-Object -TypeName PSObject -Property $props

$count ++
} |
Export-Csv -Path users.csv –NoTypeInformation

You need a counter variable. Use Get-ADUser to get you test users and pipe to foreach. You can then create a hashtable for the properties – Id = samaccountname, Division is an empty string and EmployeeNumber is set equal to count.

A switch statement is used to modify the division. Modulo 3 on the $count variable can only give values of 0, 1 or 2.

Remove the EmployeeNumber for every 15th account

Create an object. Increment the counter.

Export the objects to a csv file.  You should end up with something like this:

Division   EmployeeNumber Id
--------   -------------- --
Division B 1              DonJones
Division C 2              DonSmith
Division A 3              DonBrown
Division B 4              DonBlack
Division C 5              DonWhite
Division A 6              DonGreen
Division B 7              DonWood
Division C 8              DonBell
Division A 9              DonHarris
Division B 10             DonFox
Division C 11             JamesJones
Division A 12             JamesSmith
Division B 13             JamesBrown
Division C 14             JamesBlack
Division A                JamesWhite
Division B 16             JamesGreen
Division C 17             JamesWood

etc.

Next time I’ll show you how to deal with the missing employee numbers when you modify the AD accounts.

Get-Content and Numbers

A common technique is to put a list of information into a text file and read that using Get-Content. The information is often server names. This works great when the data is strings but breaks down if you’re dealing with numbers.

Lets start with a text file containing some numbers:

PS> Get-Content -Path .\num.txt
1000
109
258
331
699
744
829

Let’s try and sort those numbers:

PS> Get-Content -Path .\num.txt | sort
1000
109
258
331
699
744
829

It may seem that nothing is happening but if you sort descending:

PS> Get-Content -Path .\num.txt | sort -Descending
829
744
699
331
258
109
1000

Things change but not quite in the way that you might think. The data isn’t being sorted numerically its being sorted as strings.

PS> Get-Content -Path .\num.txt | Get-Member
TypeName: System.String

If you read the help file for Get-Content it states that the cmdlet returns either strings or bytes depending on the content of the file.

So if Get-Content returns strings what can we do if we want to work with the file contents as numbers rather than text.

You have a few choices. You can force a type conversion (known in programming circles as a cast) in a number of ways.

First using the –as operator

PS> Get-Content -Path .\num.txt | foreach {$psitem -as [int]} | sort
109
258
331
699
744
829
1000

That’s more like a numeric sort

You can cut down on the code:

PS> Get-Content -Path .\num.txt | foreach {[int]$psitem} | sort
109
258
331
699
744
829
1000

Or you can use foreach to call  a method on the object

PS> Get-Content -Path .\num.txt | foreach ToInt32($psitem) | sort
109
258
331
699
744
829
1000

You can even use the foreach method on the array that Get-Content creates

PS> (Get-Content -Path .\num.txt).foreach({[int]$psitem})  | sort
109
258
331
699
744
829
1000

The last option is the fastest but is only available on PowerShell 4.0 and above.

Get-Content returns strings but its not difficult to turn them into the numbers you need

Filtering of Objects and Properties

Saw a post on the forum today that suggests people are still confused about how to perform filtering of objects and properties in PowerShell.

As with so much in PowerShell explanations are always better with examples.

 

Let’s start with the physical disks in a computer:

PS> Get-PhysicalDisk

FriendlyName               SerialNumber    CanPool OperationalStatus HealthStatus Usage            Size 
------------               ------------    ------- ----------------- ------------ -----            ---- 
Toshiba USB 2.0 Ext. HDD   WD-WCAMR3209671 False   OK                Healthy      Auto-Select 298.09 GB 
ST916082 1A                DEF10E8D9B36    False   OK                Healthy      Auto-Select 149.05 GB 
Samsung SSD 840 PRO Series S1AXNSAF329511V False   OK                Healthy      Auto-Select 476.94 GB

 

If you want to objects that match a specific criteria – for instance disk is larger than 300GB:

PS> Get-PhysicalDisk | Where-Object Size -gt 300GB

FriendlyName               SerialNumber    CanPool OperationalStatus HealthStatus Usage            Size 
------------               ------------    ------- ----------------- ------------ -----            ---- 
Samsung SSD 840 PRO Series S1AXNSAF329511V False   OK                Healthy      Auto-Select 476.94 GB

 

Where-Object is your friend.

 

What you’re actually doing – though very few people actually write it like this – is

Get-PhysicalDisk | Where-Object -Property Size -GT -Value 300GB

 

The help file for Where-Object lists the possible operators.

 

You can also show the original style syntax

Get-PhysicalDisk | Where-Object -FilterScript {$_.Size -gt 300GB}

 

Normal usage is to not write the –FilterScript parameter so it becomes

Get-PhysicalDisk | Where-Object {$_.Size -gt 300GB}

 

$_ represents the object currently on the pipeline. If you need to use multiply conditions in your filter you’ll need to use the older style syntax.

 

So far you’ve seen how reduce the number of objects on the pipeline. Where-Object filters out those that don’t match the given criteria.

 

If you want to reduce the number of properties that the objects on the pipeline possess you’ll need to use Select-Object

PS> Get-PhysicalDisk | Select-Object -Property FriendlyName, HealthStatus, Size

FriendlyName               HealthStatus         Size 
------------               ------------         ---- 
Toshiba USB 2.0 Ext. HDD   Healthy      320072933376 
ST916082 1A                Healthy      160041885696 
Samsung SSD 840 PRO Series Healthy      512110190592

 

More commonly written as

Get-PhysicalDisk | Select FriendlyName, HealthStatus, Size

 

PowerShell best practice is always to use the full cmdlet and parameter names in your scripts. The *-Object cmdlets and in particular Where-Object, Sort-Object and Select-Object are often abbreviated to Where, Sort and Select and the parameters only used where necessary.  This was the way I was advised to use them by Jeffrey Snover – who invented PowerShell -  when I wrote PowerShell in Practice. Good enough for me.

Append data to a file

A question on the forums - the user wanted to append data to a file. This is a common scenario when you’re creating a log file.

 

There’s 2 easy ways to do this.

 

Lets create a couple of variables with multi-line data

PS> $data = @'
>> This is
>> multiline data
>>
>> '@
PS> $data
This is
multiline data

 

PS> $data2 = @'
>> This is
>> more multiline
>> data
>> '@
PS> $data2
This is
more multiline
data

 

First you could use Out-File

PS> Out-File -FilePath of.txt -InputObject $data
PS> Out-File -FilePath of.txt -InputObject $data2 -Append
PS> Get-Content -Path of.txt
This is
multiline data

This is
more multiline
data

 

First time you call Out-File you don’t have to use –Appemd but you can. On subsequent calls use -Append to add the data – if you don’t the file will be overwritten with the new data.

 

Second option is one you don’t see so much – Add-Content. In earlier versions of PowerShell this was your only option

PS> Add-Content -Path ac.txt -Value $data
PS> Add-Content -Path ac.txt -Value $data2
PS> Get-Content -Path ac.txt
This is
multiline data

This is
more multiline
data

 

If the file doesn’t exist Add-Content will create it.

 

Two ways to append data to a file

Preserving property order

This is a very common pattern:

 

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$comp = Get-CimInstance -ClassName Win32_ComputerSystem

$props = @{
OS = $os.Caption
InstallDate = $os.InstallDate
LastBoot = $os.LastBootUpTime
Make = $comp.Manufacturer
Model = $comp.Model
}

New-Object -TypeName PSObject -Property $props

 

Get some data – in this case a couple of WMI classes and create an output object.

Unfortunately, the output looks like this

 

Make        : Microsoft Corporation
Model       : Surface Pro 2
LastBoot    : 30/12/2016 09:41:52
OS          : Microsoft Windows 10 Pro Insider Preview
InstallDate : 08/12/2016 13:20:04

 

The property order is NOT preserved because you’re working with a hash table. If you absolutely have to preserve the property order use an ordered hash table

 

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$comp = Get-CimInstance -ClassName Win32_ComputerSystem

$props = [ordered]@{
OS = $os.Caption
InstallDate = $os.InstallDate
LastBoot = $os.LastBootUpTime
Make = $comp.Manufacturer
Model = $comp.Model
}

New-Object -TypeName PSObject -Property $props

 

All you do is add [ordered] in front of the hashtable definition and your output becomes

 

OS          : Microsoft Windows 10 Pro Insider Preview
InstallDate : 08/12/2016 13:20:04
LastBoot    : 30/12/2016 09:41:52
Make        : Microsoft Corporation
Model       : Surface Pro 2

 

exactly what you defined

Calculating Standard Deviation – the class

You’ve seen how to calculate standard deviation and how to turn that calculation into a PowerShell function. This time we’ll use the calculation to create a class:

class stats {

static [double] StandardDeviation ([double[]]$numbers) {

$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}

$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )

return [math]::Round($sigma, 3)

}
}

 

Name the class stats. We’ll create a static method on the class that will calculate the Standard Deviation. The method takes an array of doubles as input – integers are converted automatically to double.

 

The calculation is the same as before. Just add the return keyword to ensure you actually get the output. Using return is mandatory in PowerShell classes.

 

Because its a static class you don’t need to create an instance of the class – just use it directly:

$numbers = 1..10
[stats]::StandardDeviation($numbers)
[stats]::StandardDeviation(1..10)

Calculating Standard Deviation – the function

Last time I showed how to calculate the standard deviation of a set of numbers and said the code could easily be turned into a function

function Measure-StandardDeviation {
[CmdletBinding()]
param (
[double[]]$numbers
)
$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}


$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )
[math]::Round($sigma, 3)
}

 

I’ve called the function Measure-StandardDeviation based on Measure-Object producing the mean.

The parameter is set to accept an array of doubles – floating point numbers – integers will be automatically converted to doubles.

To use the function

$numbers = 1..10
Measure-StandardDeviation -numbers $numbers

 

or create the input array directly

Measure-StandardDeviation -numbers (1..10)

Calculating Standard Deviations–the calculation

A while ago I saw something asking about calculating standard deviations for a set of numbers in PowerShell. You can calculate the the mean (average) of a set of numbers using Measure-Object

 

$numbers = 1..10


$mean = $numbers | Measure-Object -Average | select -ExpandProperty Average
$mean
5.5

 

To calculate the standard deviation you:

Subtract the mean of the set from each number in the set and square the difference. Then calculate the mean of those squared differences and finally take the square root of the result.

We already have the mean

 

so lets calculate the differences and square them

$sqdiffs = $numbers | foreach {[math]::Pow(($psitem - $mean), 2)}

 

The Pow static method of the math class is used to create the squares. The standard deviation (normally referred to as sigma) is calculated:

$sigma = [math]::Sqrt( ($sqdiffs | Measure-Object -Average | select -ExpandProperty Average) )
[math]::Round($sigma, 3)

 

You can calculate the mean of the squares of the differences using measure-object again. I used the Round static method of the math class to give my answer 3 decimal places.

 

Now that we can calculate the standard deviation we could turn this into a method, or a PowerShell class or even create a proxy function for Measure-Object that enables you to calculate standard deviation.

using help proactively

I was reading a thread on the forum about using a particular cmdlet and it occurred to me that the person posing the question hadn’t actually looked at the help file for the cmdlet.

 

One of the first things I do when coming across a new cmdlet – either in some code or when I need to perform a new task - is to read the FULL help file for the cmdlet.

Either 

Get-Help Get-Process –Full

 

or

Get-Help Get-Process –Online

 

The online version tends to be updated quicker than the downloadable version.

 

Two areas to concentrate on.

The parameter list shows you what parameters are available, the input and relevant information

 

The examples section is possibly the most useful as you’ll find how you can actually use the cmdlet.

 

The detailed description and notes a re worth a read – especially for a cmdlet you’ve not used before

 

Think about the PowerShell experts you’ve come across. How did they get to be experts? A lot of it was reading the help files.