Take multiple screenshots

Version: 1.2.2
Creator Name: Ton de Vreede
Date Created: 2019-03-13
Date Modified: 2019-03-31
Scripting language: PS
Download Count: 59

This script gets the dimensions of the users working display area and takes screenshots. The screenshots can be saved as a BMP, JPG or PNG in a location of choice.
Tags: screenshot, user experience, vdi, rds, citrix

The Script

$ErrorActionPreference = 'Stop'
<#
    .SYNOPSIS
    This script will take multiple screenshots of all displays of the user session.

    .DESCRIPTION
    This script gets the dimensions of the users working display area and takes screenshots. The screenshots can be saved
    as a BMP, JPG or PNG in a location of choice. 

    .PARAMETER strScreenShotPath
    The location the screenshot should be saved, without a trailing backslash (ie. \\server\sahare\screenshots). Environment
    variables such as %USERPROFILE% may be used.

    .PARAMETER strImageType
    The desired screenshot format.
    BMP --> Large, no compression (only included for compatibility)
    JPG --> Smallest, compression artifacts
    PNG --> Larger than JPG, but losslesscompression, default

    .PARAMETER intScreenShotAmount
    The amount of screenshots to take.

    .PARAMETER intScreenShotInterval
    The time between each screenshot, in seconds.

    .EXAMPLE
    Example is not relevant as this script will be called through ControlUp Console

    .NOTES
    The working display area of a session is affected by the Scaling choice in Display settings. For example, a user
    display may be 1920x1080 with 125% scaling will result in a 1536x864 screenshot
    If the screenshots are written to a shared location make sure users can write and modify in that location, but not
    read to make sure they cannot open other users's screenshots.
#>

[string]$strScreenShotPath = $args[0]
[string]$strImageType = $args[1]
[int]$intScreenShotAmount = $args[2]
# Sleeptimer uses milliseconds, convert now
[int]$intScreenShotInterval = ($args[3] * 1000)

Function Feedback {
    Param (
        [Parameter(Mandatory = $true,
            Position = 0)]
        [string]$Message,
        [Parameter(Mandatory = $false,
            Position = 1)]
        [string]$Exception,
        [switch]$Oops
    )

    # This function provides feedback in the console on errors or progress, and aborts if error has occured.
    If (!$error -and !$Oops) {
        # Write content of feedback string
        Write-Host $Message -ForegroundColor 'Green'
    }

    # If an error occured report it, and exit the script with ErrorLevel 1
    Else {
        # Write content of feedback string but to the error stream
        $Host.UI.WriteErrorLine($Message) 
        
        # Display error details
        If ($Exception) {
            $Host.UI.WriteErrorLine("Exception detail:`n$Exception")
        }

        # Exit errorlevel 1
        Exit 1
    }
}

function Save-BitmapObject {
    param (
        [parameter(Mandatory = $true,
            position = 0)]
        [System.Drawing.Bitmap]$Bitmap,
        [parameter(Mandatory = $true,
            position = 1)]
        [string]$FilePathAndName,
        [parameter(Mandatory = $true,
            position = 2)]
        [string]$FileType
    )
    switch ($FileType) {
        "BMP" { $Bitmap.Save("$FilePathAndName", ([System.Drawing.Imaging.ImageFormat]::Bmp)) }
        "PNG" { $Bitmap.Save("$FilePathAndName", ([System.Drawing.Imaging.ImageFormat]::Png)) }
        "JPG" { $Bitmap.Save("$FilePathAndName", ([System.Drawing.Imaging.ImageFormat]::Jpeg)) }
    }
}

# Check all the arguments have been passsed
<#if ($args.Count -ne 4) {
    Feedback -Message 'The script did not get enough arguments from the Console.' -Oops
}#>

# Set filename
[string]$strNow = (Get-Date).ToString("yyyyMMdd-HHmmss")
$strScreenShotFileName = "$strNow-%COMPUTERNAME%-%USERNAME%"

# Take screenshot(s) and save them
# Load the assemblies required for using screen and form calls
try {
    [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
}
catch {
    Feedback -Message 'Required assemblies could not be loaded.' -Oops
}

# Get the bounds of the entire display area
try {
    $Screens = [System.Windows.Forms.Screen]::AllScreens

    [int]$intRightMostMonitorStart = $screens.Bounds.X | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
    [int]$intWidth = $intRightMostMonitorStart + ($Screens | Where-Object { $_.Bounds.X -eq $intRightMostMonitorStart }).Bounds.Width

    [int]$intTopMostMonitorStart = $screens.Bounds.Y | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
    [int]$intHeight = $intTopMostMonitorStart + ($Screens | Where-Object { $_.Bounds.X -eq $intTopMostMonitorStart }).Bounds.Height
}
catch {
    Feedback -Message 'The dimensions of the entire display area could not be retreived.' -Oops
}

# Take the screenshot 
try {

    $bmpScreenShot = New-Object System.Drawing.Bitmap $intWidth, $intHeight
    $sizScreenShot = New-Object System.Drawing.Size $intWidth, $intHeight
    $imgScreenshot = [System.Drawing.Graphics]::FromImage($bmpScreenShot)
    [datetime]$dtScreenShot = Get-Date
    $imgScreenShot.CopyFromScreen(0, 0, 0, 0, $sizScreenShot)        
}
catch {
    Feedback -Message 'There was a problem taking the screenshot.' -Oops
}

# Save the first screenshot to desired path. Change path to %TEMP% if chosen path can't be written to
try {
    $strFilePathAndName = ([System.Environment]::ExpandEnvironmentVariables("$strScreenShotPath\$strScreenShotFileName"))
    Save-BitmapObject -Bitmap $bmpScreenShot -FilePathAndName "$strFilePathAndName-1.$strImageType" -FileType $strImageType
}
catch {
    # Failure perhaps because of filepath problem, revert to %TEMP% if this was not the filepath already
    if ($strScreenShotPath -ne '%TEMP%') {
        # Clear the error raised by first save attempt failure
        $error.clear()

        # Try saving to %TEMP% instead
        try {
            $strFilePathAndName = ([System.Environment]::ExpandEnvironmentVariables("%TEMP%\$strScreenShotFileName"))
            Save-BitmapObject -Bitmap $bmpScreenShot -FilePathAndName "$strFilePathAndName-1.$strImageType" -FileType $strImageType
        }
        catch {
            Feedback -Message 'Screenshots could not be saved.' -Oops
        }
    }
    else {
        # The file path the screenshot was supposed to be saved to was already %TEMP%, so something else was going on.
        Feedback -Message 'Screenshots could not be saved.' -Oops
    }
}

# Everything seems to be working fine, take more screenshots and save them if more than one was specified
If ($intScreenShotAmount -gt 1) {
    for ($i = 2; $i -le $intScreenShotAmount; $i++) {
        # Calculate the interval, based on the time the CopyFromScreen and Save file took. If it took longer than then screenshot interval set the sleep timer to 0 seconds.
        [int]$intSleepMilliseconds = ([timespan](New-TimeSpan -Start $dtScreenShot -End $(Get-Date))).TotalMilliseconds

        # Test if less time has passed than sepcified interval, if so sleep remainder of time
        if ($intSleepMilliseconds -lt $intScreenShotInterval) { Start-Sleep -Milliseconds ($intScreenShotInterval - $intSleepMilliseconds) }

        # Interval has passed, set datetime of screenshot, take the screenshot and save it
        $dtScreenShot = Get-Date
        $imgScreenShot.CopyFromScreen(0, 0, 0, 0, $sizScreenShot)

        # Save it
        Save-BitmapObject -Bitmap $bmpScreenShot -FilePathAndName "$strFilePathAndName-$($i).$($strImageType)" -FileType $strImageType
    }
}
else {
    Feedback -Message "Screenshot saved to $strFilePathAndName-1.$strImageType"
    Exit 0
}

# Script has not exited, so multiple screenshots have been taken.
Feedback -Message "Screenshots saved to $([System.Environment]::ExpandEnvironmentVariables("$strScreenShotPath"))"