Powershell

Powershell Template

Writing a powershell script just looks much prettier if you start it with this template:

<#
.SYNOPSIS


.DESCRIPTION


.INPUTS


.OUTPUTS


.NOTES


.EXAMPLE


#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
#region parameters

#endregion parameters
$ErrorActionPreference = "Stop"

#----------------------------------------------------------[Declarations]----------------------------------------------------------
#region variables

#endregion variables


#-----------------------------------------------------------[Functions]------------------------------------------------------------
#region functions

#endregion functions


#-----------------------------------------------------------[Execution]------------------------------------------------------------
#region script main

#endregion script main

Powershell Module Template

It is by far more preferable to use Powershell modules instead of individual scripts. Modules can easily be installed on a developer workstation or as part of a pipeline run which means all the functions that you add to the module becomes available to you during that pipeline. Ideally you should publish a versioned copy of the module to an artifact store (Azure DevOps has a great one.) and then use that instead. Below is a great example of a template for a powershell module. Using modules helps keeping your code DRY

<#
.SYNOPSIS
    Module for whatever you are making a module for.

.EXAMPLE
    Import-Module <MODULE_NAME>

.DESCRIPTION
    Some text to explain the main theme for the module

.NOTES
    Some notes about the module

.LINK
    https://docs.driesventer.com
#>

#region Invoke-FunctionA
function Invoke-FunctionA
<#
.SYNOPSIS
    Module for whatever you are making the Function for.
.EXAMPLE
    Invoke-FunctionA -hello <insert string>
.DESCRIPTION
    Some text to explain the main inner workings of the function
.OUTPUT
    Describe the output of the function including type
.NOTES
    Some notes about the function
#>
{
    [CmdletBinding(
        # SupportsShouldProcess=$True # enable if you need to support '-whatif'
    )]
    param (
        [parameter(Mandatory=$true)] [string] $hello
    )

    # return
    return Invoke-FunctionA -hello $hello
}
#endregion Invoke-FunctionA

#region Invoke-FunctionB
function Invoke-FunctionB
<#
.SYNOPSIS
    Module for whatever you are making the Function for.
.EXAMPLE
    Invoke-FunctionA -hello <insert string>
.DESCRIPTION
    Some text to explain the main inner workings of the function
.OUTPUT
    Describe the output of the function including type
.NOTES
    Some notes about the function
#>
{
    [CmdletBinding(
        # SupportsShouldProcess=$True # enable if you need to support '-whatif'
    )]
    param (
        [parameter(Mandatory=$true)] [string] $hello
    )

    # return
    return Invoke-FunctionB -hello $hello
}
#endregion Invoke-FunctionB

#region Exported Functions
# public (exported)
Export-ModuleMember -function Invoke-FunctionA
Export-ModuleMember -function Invoke-FunctionB
#endregion Exported Functions


General Commands

Tee-Object -Variable

Very few engineers use tee-object -variable command, it is super useful when running scripts that you want to see the output of in script output. Variables commonly get assigned as follows:


$someVariable = "something"
-or
$process = get-process

In the process example above you don't get to see the processes displayed in the output with out explicitly writing the object to the output in a separate command. Like this:


$process = get-process
$process

With the Tee-object you can see the output and capture it as a variable in one go. Like this:

Get-Process | Tee-Object -Variable process

Give it a go.


Arrays

Having this as a reference really helps:

#ARRAY Example
# Reference: https://ss64.com/ps/syntax-arrays.html
$arrayObjects = @("a", "b", "c")
$arrayObjects.GetType()
foreach-object -process {
    clear-host
    write-host $objectkey
    start-sleep -Seconds 2
}
#Add and remove from an array
$Fruits.Add("Kiwi")
$Fruits.Remove("Apple")

Hash Table

$hash = @{}
$hash.Add("Key", "Value")

Azure

Authentication

Create a new Service Principal on Azure

Todo: add some powershell

Login as service principal into Azure

Logon with SP is particularly useful if you want to test if a certain ADO service connection has the correct right to perform a certain action. You will need to create a temp secret on the Azure App Registration object first. Then using the application ID as the user and the secret as the password :

$pscredential = Get-Credential -UserName 60df9dae-1ebf-4b8f-a01b-cab536ed1e7a<Principal_ID>

#Output
PS C:\repos\XLAB_Docs> $pscredential = Get-Credential -UserName 60df9dae-1ebf-4b8f-a01b-cab536ed1e7a

PowerShell credential request
Enter your credentials.
Password for user 60df9dae-1ebf-4b8f-a01b-cab536ed1e7a: ***********************

Then execute the following to get the tenant Id and connect as a service principal:

(Get-AzContext).Tenant.Id | tee-object -variable tenantId
Connect-AzAccount -ServicePrincipal -Credential $pscredential -Tenant $tenantId

Generate a new self signed certificate for Azure VNet Gateway Point to Site

#Create the root cert
$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature `
-Subject "CN=XlabP2SRootCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My"  `
-KeyUsageProperty Sign -KeyUsage CertSign
# When you export this cert don't export the private key and export as base 64

# Create Client Cert
New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature `
-Subject "CN=XlabP2SClientCert1" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")
#when exporting this cert includes the private key and export to pfx

#Create Client Cert from an existing root certificate
$cert =get-ChildItem -Path Cert:\CurrentUser\My\029EE5C879B45753BF70572BC08EBB5B4EEC5B1F # you should be able to tab out the certificate thumbprint this should be the root cert
#Then create a new client certificate that is signed by the root
New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature `
-Subject "CN=XlabP2SClientCert1" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")

CodeSnippets

Generate Passwords

function New-RandomPassword {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false)] [bool] $sqlFriendly = $false,
        [Parameter(Mandatory=$false)] [int] $passwordLength = 16
    )
    if ($sqlFriendly -ne $false) {
        # Generates a complex password that has a limited set of special characters
        Write-output "Generating SQL Friendly Complex Password that contains a subset of characters"
        $password = ([char[]]([char]33) + [char[]]([char]35..[char]38) + [char[]]([char]35..[char]38) + [char[]]([char]35..[char]38) + [char[]]([char]65..[char]90) + [char[]]([char]97..[char]122) + 0..9 | sort {Get-Random})[0..$passwordLength] -join ''
        return $password
    }else {
        # Generates a complex password
        Write-output "Generating Random Complex Password"
        $password = ([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | sort {Get-Random})[0..$passwordLength] -join ''
        return $password
    }
}

Get all Role Definitions export to CSV

# Get all Azure role definitions
$roles = Get-AzRoleDefinition
# Create an array to hold the data
$data = @()
# Loop through each action and add the data to the array
foreach ($action in $roles.Actions) {
    $roleData = [ordered]@{
        "Action" = $action
    }
    # Loop through each role and add it to the role data
    foreach ($role in $roles) {
        $roleData[$role.Name] = if ($role.Actions -contains $action) { "Yes" } else { "No" }
    }
    # Add the role data to the array
    $data += New-Object PSObject -Property $roleData
}
# Export the data to a CSV file
$data | Export-Csv -Path ".\RolePermissions.csv" -NoTypeInformation

Get Roles for specific actions

function Get-ActionRoles {
param(
    [Parameter(Mandatory = $true)][string]$Action
)
<#
.SYNOPSIS
    Get all Azure roles that have a specified action
.DESCRIPTION
    This function will return all Azure roles that have a specified action.
.PARAMETER Action
    The action to search for. This can be a single action or a wildcard.
.EXAMPLE
    Get-ActionRoles -Action "Microsoft.Compute/virtualMachines/*"
    This example will return all Azure roles that have the action "Microsoft.Compute/virtualMachines/*"
#>
# Get all Azure role definitions
$roles = Get-AzRoleDefinition
# Create an array to hold the data
$data = @()
# Loop through each role and add it to the role data if it has the specified action
foreach ($role in $roles) {
    if ($role.Actions -contains $Action) {
        $roleData = [ordered]@{
            "Role Name"   = $role.Name
            "Role ID"     = $role.Id
            "Description" = $role.Description
        }
        # Add the role data to the array
        $data += New-Object PSObject -Property $roleData
    }
}
# Output the data
$data | Format-Table -AutoSize
}