Powershell foreach

The powershell foreach loop must be one of the most powerful tools in any scripting toolkit. It allows you to easily repeat a list of actions multiple times.

below is a classic example that highlights the power of the foreach loop:

$servers = "Srv101","Srv102","Srv103"

foreach ($server in $servers)
     invoke-command -ComputerName $server -ScriptBlock
         Install-WindowsFeature  BitLocker

         New-Item HKLM:SOFTWARE/Policies/Microsoft/FVE
         Set-Location HKLM:SOFTWARE/Policies/Microsoft
         Set-ItemProperty FVE -Name UseAdvancedStartup -Value 1
         Set-ItemProperty FVE -Name EnableBDEWithNoTPM -Value 1

In the above script we effectively install Bitlocker, configure the registry to allow encryption without the use of a TPM chip and reboot the computer to make sure the bit locker install completes properly. On 3 machines using one script!

When using the foreach loop you’ll often be also using arrays, so here are some tips on using arrays:

quick and dirty integer array using simple comma notation:

$myarray = 1,2,3

more elaborate syntax achieving the same result:

$myarray = @(1,2,3)

if you need something more elaborate, then I suggest you look into ConvertFrom-Json which will give you a very powerful way to manage configurations and multiple arrays.

Config Values for Powershell Scripts

In this post I will share my favourite config approach to Powershell scripts

O ften you’ll find yourself writing one set of scripts that will be used for several machines or environments. Most of the commands will be the same for each server, just a few changes needed. If you’ve used a scripted installation tool for SharePoint, such as autospinstaller, then you’ll be all too familiar with using a config file. Autospinstaller uses an xml file to store its config values. while that’s ok, I find it becomes very hard to read and decypher. I much prefer using JSON notation instead.  Here is an example to get you started:

Create a config.json file using your favourite text editor. notice following key syntax elements:

  • All parameter names and values need to be in quotes. only integer values can/must skip the quotes.
  • The whole content needs to be wrappen in one set of curlies {}
  • arrays of sub objects, as in the disks array uses square brackets []
  • each sub object needs to be embedded in curlies {}
   "domain": "DMZ",
   "Name": "SQL01",
   "Role": "SQL",
   "IP" : "",
   "DNS" : "",
   "GateWay" : "",
   "Description": "DMZ SQL Server 2016",
   "Disks": [
     "ID": 0,
     "Name": "OS",
     "SizeInBytes": 274877906944,
     "Partitions": [
        "ID": 0,
        "Letter": "C",
        "Size": 261872,
        "Label": "OS"
     "ID": 1,
     "Name": "Data",
     "SizeInBytes": 549755813888,
     "Partitions": [
       "ID": 0,
       "Letter": "D",
       "Size": 524288,
       "Label": "Data"

then you can import it into your powershell script, best via a command parameter. InitVM.ps1, and start addressing the values using simple dot notation:

$config = Get-Content -Raw -Path $configPath | ConvertFrom-Json
 New-NetIPAddress –InterfaceAlias "Ethernet" –IPAddress $config.IP –PrefixLength 24 -DefaultGateway $config.Gateway
 Set-DnsClientServerAddress –InterfaceAlias "Ethernet" -ServerAddresses ($config.DNS) 
 Rename-Computer -NewName $config.Name -Restart 

foreach ($disk in $config.Disks | Where {$_.ID -gt 0})
   Set-Disk $disk.ID -isOffline $false
   Get-Disk $disk.ID | Initialize-Disk -PartitionStyle GPT
   foreach ($partition in $disk.Partitions)
      $sizeInBytes = $partition.Size * 1048576 
      New-Partition -DiskNumber $disk.ID -Size $sizeinBytes -DriveLetter $partition.Letter | Format-Volume -FileSystem NTFS -NewFileSystemLabel $partition.Label

The above script is quite useful if you’re building machines from scratch. It first sets the IP address, with Gateway and DNS, then renames the computer. finally it initialises the disks and formats the volumes based on your configuration.

Need to restart the machine to make the name change stick though. so if you plan on doing more, you might want to consider splitting tasks into multiple scripts. thanks to your new config file approach, all you need to do is re-import the config and continue where you left off.

That’s it, now you can execute the script using a simple parameter and use a different config for each server/environment/farm:

InitVM.ps1 -configPath config.json