~/jackwent.co.uk
jack@jackwent:~$ cat scripting/

Scripting

by published 17 March 2024updated 11 October 20264 min read

This is where I keep the scripts I’ve written over the years. In 20+ years of IT I’ve gone from frontline help desk support to designing complex system architectures, and the one skill that’s helped at every step is scripting. It saves time, cuts out mistakes, and teaches you a lot about the systems you’re working on.

I’m not going to pretend I’m an expert. Most of what I know about PowerShell, Bash and Python comes from curiosity, trial and error, and wanting to stop doing the same job by hand over and over. Every script here started as a real problem I needed to solve.

So what you’ll find is a collection of scripts, each written because I needed it. Some are simple automation, some go a bit further, and they’re all part of how I keep learning. I hope they save you some time.

Whether you’re new to scripting and looking for somewhere to start, or you’ve been doing it for years and have better ways of doing things, I’d love to hear from you. Share what you know, tell me where I could improve, and let’s learn from each other.

PowerShell

CodeSign

This script signs a PowerShell script with a code signing certificate stored in a PFX (Personal Information Exchange) file.

# Change "Script-Name-Here.ps1" for you script file name!
# Specify the path to the PFX file containing the code signing certificate
$PfxFilePath = "C:\CodeSign\codesign-cert.pfx"

# Check if the PFX file exists
if (-not (Test-Path -Path $PfxFilePath -PathType Leaf)) {
    Write-Host "Error: PFX file not found at $PfxFilePath" -ForegroundColor Red
    return
}

try {
    # Get the certificate from the PFX file
    $certificate = Get-PfxCertificate -FilePath $PfxFilePath

    # Specify the path to the script to be signed
    $ScriptPath = ".\Script-Name-Here.ps1"

    # Check if the script file exists
    if (-not (Test-Path -Path $ScriptPath -PathType Leaf)) {
        Write-Host "Error: Script file not found at $ScriptPath" -ForegroundColor Red
        return
    }

    # Sign the script with the specified certificate and timestamp server
    Set-AuthenticodeSignature -FilePath $ScriptPath -Certificate $certificate -IncludeChain All -TimestampServer "http://timestamp.digicert.com"

    # Display the details of the code signing
    Get-AuthenticodeSignature $ScriptPath -Verbose | Format-List

} catch {
    Write-Host "Error occurred: $_" -ForegroundColor Red
}

Windows uptime

Getting an accurate uptime on Windows 10 and 11 is surprisingly tricky when Fast Boot (also called Quick Boot) is turned on, because of the way it changes how Windows shuts down and starts up.

Fast Boot, also known as Hybrid Boot or Fast Startup, arrived in Windows 8 and is still there in Windows 10 and 11. With it switched on, Windows doesn’t fully shut down. It hibernates the kernel session instead, saving it to the hiberfile when you shut down and restoring it when you start up again, which is why boot times are faster.

The catch is that it throws off uptime monitoring. Because the kernel session is hibernated instead of shut down, the usual ways of tracking uptime, like checking the boot time in the event logs or system information, often don’t show how long the machine has really been up.

So I wrote this PowerShell script. It searches the Windows Event Log for specific boot and shutdown events and outputs the time of the most recent match.

# Define the Event IDs and messages to search for
$eventIDs = @(27, 1074)
$messages = @("The boot type was 0x0", "The boot type was 0x1", "The boot type was 0x2", "Shut-down type: power off")

# Initialize variable to store the latest event time
$latestEventTime = [datetime]::MinValue

# Get the events from the Event Log
foreach ($eventID in $eventIDs) {
    $events = Get-WinEvent -FilterHashtable @{LogName='System'; ID=$eventID} -ErrorAction SilentlyContinue

    # Loop through each event and check if the message matches
    foreach ($event in $events) {
        $message = $event.Message
        foreach ($searchMessage in $messages) {
            if ($message -match $searchMessage) {
                $eventTime = $event.TimeCreated
                if ($eventTime -gt $latestEventTime) {
                    $latestEventTime = $eventTime
                }
            }
        }
    }
}

# Output the latest event time, if any matching events were found
if ($latestEventTime -ne [datetime]::MinValue) {
    write-output "Last Boot/Reboot: $latestEventTime"
}

Windows Firewall status

This script defines a function, Check-FirewallEnabled, that takes a $ProfileName parameter for the firewall profile. It checks the firewall status for that profile using the Get-NetFirewallProfile cmdlet, then outputs the status of each profile.

# Function to check if firewall is enabled for a given profile
Function Check-FirewallEnabled {
    param (
        [string]$ProfileName
    )
    
    $FirewallStatus = Get-NetFirewallProfile -Name $ProfileName | Select-Object -ExpandProperty Enabled
    if ($FirewallStatus -eq 'True') {
        $Output = "Enabled"
    } else {
        $Output = "Disabled"
    }
    Write-Output "$ProfileName Firewall: $Output"
}

# Call the function to check Firewall status for each profile
Check-FirewallEnabled -ProfileName 'Domain'
Check-FirewallEnabled -ProfileName 'Public'
Check-FirewallEnabled -ProfileName 'Private'

BitLocker drive encryption status

This script defines a function, Check-DriveEncryption, that takes a $DriveLetter parameter for the drive you want to check. It uses the Get-BitLockerVolume cmdlet to see whether BitLocker is enabled on that drive, and outputs “Encrypted” or “Not Encrypted”. Note that this script needs to be run as administrator.

# Function to check if a drive is encrypted
Function Check-DriveEncryption {
    param (
        [string]$DriveLetter
    )

    $Drive = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='$DriveLetter'"
    
    # Check if BitLocker is enabled on the drive
    $BitLockerStatus = Get-BitLockerVolume -MountPoint $Drive.DeviceID -ErrorAction SilentlyContinue
    if ($BitLockerStatus) {
        $Output = "Encrypted"
    } else {
        $Output = "Not Encrypted"
    }

    Write-Output "$($Drive.DeviceID) $Output"
}

# Get a list of all drive letters on the device
$DriveLetters = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } | Select-Object -ExpandProperty DeviceID

# Call the function to check encryption status for each drive
foreach ($DriveLetter in $DriveLetters) {
    Check-DriveEncryption -DriveLetter $DriveLetter
}

Bash

Update check

Save this script to a file (for example check_updates.sh), make it executable with chmod +x check_updates.sh, then run it with ./check_updates.sh. It checks apt-show-versions is installed, updates the package lists, uses apt-show-versions to list the available updates, then counts them and tells you how many there are.

#!/bin/bash

# Check if apt-show-versions is installed
if ! command -v apt-show-versions &> /dev/null; then
    echo "Error: apt-show-versions is not installed. Please install it using 'sudo apt install apt-show-versions'."
    exit 1
fi

# Update package lists
sudo apt update > /dev/null

# Use apt-show-versions to get the list of available updates and count them
updates_count=$(apt-show-versions -u | wc -l)

# Output the number of updates available
echo "$updates_count"

Ubuntu auto update

This script updates and installs all packages on Ubuntu 22.04, with phased updates turned off. (Here’s how to turn off phased updates.) There’s a full write-up on setting it up in my Ubuntu 24.04 AutoPatch project.

#!/bin/bash

# Define a file to track progress
PROGRESS_FILE="/var/log/update-script-progress"

# Function to check if a reboot is required
check_reboot() {
    if [ -f /var/run/reboot-required ]; then
        echo "Reboot required. Rebooting now..."
        echo "step1_reboot" > $PROGRESS_FILE
        reboot
        exit 0
    fi
}

# Function to check the progress and continue where left off
check_progress() {
    if [ -f $PROGRESS_FILE ]; then
        PROGRESS=$(cat $PROGRESS_FILE)
        case $PROGRESS in
            "step1_reboot")
                echo "Continuing after first reboot..."
                step2_install_updates
                ;;
            "step2_updates")
                echo "Continuing after updates..."
                step3_install_kept_back
                ;;
            "step3_kept_back")
                echo "Continuing after installing kept-back packages..."
                check_reboot_after_updates
                ;;
            *)
                echo "Unknown progress state, starting from the beginning."
                start_script
                ;;
        esac
    else
        start_script
    fi
}

# Function to start the script
start_script() {
    check_reboot
    step2_install_updates
}

# Function to perform updates
step2_install_updates() {
    echo "Updating package lists..."
    sudo apt update

    echo "Upgrading installed packages..."
    # Set non-interactive frontend to avoid prompts
    sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --with-new-pkgs

    echo "step2_updates" > $PROGRESS_FILE
    step3_install_kept_back
}

# Function to install kept-back packages
step3_install_kept_back() {
    echo "Installing kept-back packages..."
    # Install kept-back packages by explicitly listing them
    KEPT_BACK=$(apt list --upgradable 2>/dev/null | grep -P '^\S+' -o)
    if [ -n "$KEPT_BACK" ]; then
        sudo DEBIAN_FRONTEND=noninteractive apt-get install -y $KEPT_BACK
    fi

    echo "step3_kept_back" > $PROGRESS_FILE
    check_reboot_after_updates
}

# Function to check if a reboot is required after updates
check_reboot_after_updates() {
    check_reboot
    cleanup
}

# Function to clean up unnecessary packages
cleanup() {
    echo "Cleaning up unnecessary packages..."
    sudo apt autoremove -y
    sudo apt autoclean

    echo "System is up to date. No reboot required."
    rm -f $PROGRESS_FILE
}

# Start or continue the script
check_progress
[jackwent]