Nobody’s favorite scripting language

PowerShell (.ps1) is bash for Windows (Though ironically now available for Unix variants as well). If you think of PowerShell as bash for Windows you won’t go far wrong, but it passes dotnet objects round, not text files.

One Liners

get-module -ListAvailable
Import-module pester
Remove-module pester

Running Powershell in iTerm

/usr/local/microsoft/powershell/6/pwsh ; exit;

Version compatability

Always specify the minimum version of PowerShell needed to run

#requires -version 2.0
#
# Always specify the minimum version necessary to run.
#

Null loops

# Instead of
#
{ }

# Use
#
{ $null }

# That way people know you are deliberatly doing nothing.
#

Testing Parameters

function Test-Parameters
{
<#
  .SYNOPSIS
  Validates an xml file against an xsd file.

  .DESCRIPTION

 .EXAMPLE
 
 $answer = Validate-Xml $xmlPath $xsdPath $optionalThirdParameter
#>
    param
    (
        [Parameter(Mandatory=$true)]
          [string]$xml,
        [Parameter(Mandatory=$true)]
          [string]$xsdFilePath,
        [Parameter(Mandatory=$true)]
          [ValidateScript({ Test-Path $_ })]
          [string]$DataPath,
        [Parameter(Mandatory=$false)]
          [ValidateScript({ Test-Path $_ })]
          [string]$targetNamespace
    )


    if(!(Test-Path $xsdFilePath))
    {
      throw New-Object System.IO.FileNotFoundException "xsd file $xsdFilePath not found."
    }

    # Do some real stuff.
    #
}

Display Objects

Write-Host ($obj | Format-List | Out-String)

or

Write-Host ($obj | Format-List | Out-String)

Who Am I - in Powershell

$rik = ("rik.watson" -eq [Environment]::UserName)

if ($rik)
{
  Write-Output "Environment configured for Rik Watson"

  $SsdtBiDev     = "C:\BuildAndDeploy\Unstable\SsdtBiDev"
  $SSISSolutions = "C:\SSISSolutions-TFS2013" # "\SSISSolutions"
                 # "C:\tfs\C_COMPANY_.Systems\Test\ALM-SN-SSDT-BI\SSISSolutions-TFS2013"
}
else
{
  throw "SsdtBiDev & SSISSolutions not set"
}

Modules

if (Get-Module -ListAvailable -Name AzureRM) {
    Write-Host "AzureRM Module exists"
} 
else {
    Write-Host "AzureRM Module doesn't exist, installing"
    Install-Module AzureRM -Force -Verbose
}

Ternary Operator

(PowerShell 7.0)

c$ = ($a -eq $b) ? $true : $false
    $keyStoreFlags = "PersistKeySet"
    if ($privateKeyExportable) {
        $keyStoreFlags = "Exportable,PersistKeySet"
    }
    $keyStoreFlags = ($privateKeyExportable) ? "Exportable,PersistKeySet" : "PersistKeySet"

Pester

Pester is a testing framework for Powershell.

Have a Build.ps1 which looks a little like this:

Debugging in PowerShell

Misc PowerShell scripts

ScriptCop

https://gallery.technet.microsoft.com/ScriptCop-0896dd1e

Perhaps use it’s capabilities to do things like automated testing of scripts.

Run via > Test-Module ScriptCop

Basic

# Comment

<#
Multi-line comment
#>

function Array-Test {
  $fruits = @('Apples','Oranges','Bananas')

  $fruits += 'Strawberries'

  foreach ($fruit in $fruits) {
    $fruit
  }
}


function Get-TimesResult {

  Param ([int]$a,[int]$b)
  
  $c = $a * $b
  return $c
}
  

function Get-TimesResult-Test {
  $r = Get-TimesResult -a 5 -b 5

  $r
}

Enable remote execution

Set-ExecutionPolicy RemoteSigned

Run a script, script.ps1 via

.\path_to_script\script.ps1

List of available colours

[enum]::GetValues([System.ConsoleColor]) | Foreach-Object {Write-Host $_ -ForegroundColor $_ }

And using one of them

$enabled = $false

if (! $enabled)
{
  Write-Host ("Disabled") -ForegroundColor  Yellow

  return
}

Measureing things

docker images --filter "dangling=true" -q --no-trunc | measure-object

Disk space

Get-WmiObject -Class Win32_logicaldisk
Get-Process | Get-Member
Get-Something | Get-Member
Get-Something | Select-Object -Property *
Get-Something | Select-Object -Property name

Azure & PowerShell

cd $HOME
Get-AzureRmSubscription
Select-AzureRmSubscription –SubscriptionID deadbeef-7515-49db-a399-ae041518faea 

Get-AzureRmResourceGroupDeployment -ResourceGroupName default-eun-sizing-sizemanagement-RG | measure | select Count

tidy-deployments.ps1

Param(
    [string]
    [Parameter(Mandatory = $true)]
    $subscriptionId,

    [string]
    [Parameter(Mandatory = $true)]
    $tenantId,

    [string]
    [Parameter(Mandatory = $true)]
    $resourceGroupName,

    [int]
    [Parameter(Mandatory = $true)]
    $numberOfDeploymentsToKeep,

    [int]
    [Parameter(Mandatory = $true)]
    $batchSize
)

try {
    $c = Get-AzContext
}
catch {
    $c = $null
}

if (!$c -or !$c.Account) {
    Connect-AzAccount -Subscription $subscriptionId -Tenant $tenantId
} else {
    Select-AzSubscription -Subscription $subscriptionId -Tenant $tenantId
}

# ----------------------------------
# Get Deployments
# ----------------------------------

#$dateBeforeDeleteDeployments = Get-Date -Year 2018 -Month 06 -Day 30
#$deploymentsToDelete = Get-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName | Where-Object { $_.Timestamp -le $dateBeforeDeleteDeployments }

$currentDeployments = Get-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName

$currentNumberOfDeployments = ($currentDeployments | Measure-Object).Count
$numberOfDeploymentsToRemove = $currentNumberOfDeployments - $numberOfDeploymentsToKeep

if ($numberOfDeploymentsToRemove -lt 0) {
    throw "Number of deployments to remove is < 0..."
}
if ($numberOfDeploymentsToRemove -eq 0) {
    Write-Host "Number of deployments to remove is 0..."
    return
}

Write-Host "Number of Deployments to remove: '$numberOfDeploymentsToRemove'..."

$deploymentsToDelete = $currentDeployments | Sort-Object -Property Timestamp | Select-Object -First $numberOfDeploymentsToRemove

$deploymentsToDelete | ForEach-Object {$i=0; $j=0; $deploymentsToDeleteBatched=@{}} {
    if($i -ne $batchSize -and $deploymentsToDeleteBatched["Batch $j"]) {
        $deploymentsToDeleteBatched["Batch $j"]+=$_
        $i+=1
    }
    else {
        $i=1
        $j+=1
        $deploymentsToDeleteBatched["Batch $j"]=@($_)
    }
}

Write-Host "Created $($deploymentsToDeleteBatched.Count) batches..."

# ----------------------------------
# Execute deletion in parallel
# ----------------------------------
$jobNames = @()
foreach ($batchkey in $deploymentsToDeleteBatched.Keys) {
    $deploymentsToDeleteBatch = $deploymentsToDeleteBatched.$batchkey

    $logic = {
        Param(
            [object]
            [Parameter(Mandatory = $true)]
            $ctx,

            [object]
            [Parameter(Mandatory = $true)]
            $deploymentsToDeleteBatch,

            [string]
            [Parameter(Mandatory = $true)]
            $resourceGroupName
        )

        foreach ($deploymentToDelete in $deploymentsToDeleteBatch) {
            $deploymentName = $deploymentToDelete.DeploymentName
            Remove-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -Name $deploymentName -DefaultProfile $ctx -ErrorAction Stop
            Write-Host "Deleted Deployment '$deploymentName' from '$($deploymentToDelete.Timestamp)'..."
        }  
    }

    $jobName = ([System.Guid]::NewGuid()).Guid
    $jobNames += $jobName
    $jobObject = Start-Job $logic -Name $jobName -ArgumentList (Get-AzContext), $deploymentsToDeleteBatch, $resourceGroupName
}

while (Get-Job -State "Running") {
    Write-Host "---------------------------------------------------------------"
    Write-Host "Jobs still running..."
    Get-Job | Format-Table
    Write-Host "---------------------------------------------------------------"
    Start-Sleep -Seconds 10
}

Write-Host "Jobs completed, getting output..."
Write-Host "---------------------------------------------------------------"

foreach ($jobName in $jobNames) {
    Write-Host "Output of Job '$jobName'..."
    Receive-Job -Name $jobName
    Write-Host "---------------------------------------------------------------"
}

Write-Host "Done..."

Smaller number for ` -batchSize 10 = more batches created each with batchSize` jobs to complete

az group list --subscription AssetNonProd | jq -r ".[].name"
./tidy-deployments.ps1 –SubscriptionID deadbeef-7515-49db-a399-ae041518faea -tenantId deadbeef-80ee-4819-a9ce-863d5afbea1c -resourceGroupName test-eun-sizing-sizemanagement-RG -numberOfDeploymentsToKeep 100 -batchSize 20
./tidy-deployments.ps1 –SubscriptionID deadbeef-7515-49db-a399-ae041518faea -tenantId deadbeef-80ee-4819-a9ce-863d5afbea1c -resourceGroupName test-euw-sizing-sizemanagement-RG -numberOfDeploymentsToKeep 100 -batchSize 10
Get-AzureRmResourceGroupDeployment -ResourceGroupName test-eun-sizing-sizemanagement-RG  | measure