Public/Functions/Screenshot.ps1

function Get-ScreenPNG {
<#
.SYNOPSIS
Gets ScreenPNG information.
 
.DESCRIPTION
Returns ScreenPNG data for the current system or OSD session context.
 
.PARAMETER Env
Specifies the Env to use when running Get-ScreenPNG.
 
.PARAMETER Directory
Specifies the Directory to use when running Get-ScreenPNG.
 
.PARAMETER Prefix
Specifies the Prefix to use when running Get-ScreenPNG.
 
.PARAMETER Delay
Specifies the Delay to use when running Get-ScreenPNG.
 
.PARAMETER Count
Specifies the Count to use when running Get-ScreenPNG.
 
.PARAMETER Clipboard
Specifies the Clipboard to use when running Get-ScreenPNG.
 
.PARAMETER Primary
Specifies the Primary to use when running Get-ScreenPNG.
 
.EXAMPLE
Get-ScreenPNG -Env <value>
Demonstrates a common way to run Get-ScreenPNG.
 
.LINK
https://github.com/OSDeploy/OSD/tree/master/docs
 
.NOTES
Author: David Segura - Recast Software
2026-07-13 - Initial help block created
2026-07-13 - Refined generated help text
#>

    [CmdletBinding()]
    param (
        #Directory where the Screenshots will be saved
        #Default = $Env:TEMP\Screenshots
        [string]$Directory = $null,

        #Saved files will have a Screenshot prefix in the filename
        [string]$Prefix = $null,

        #Delay before taking a Screenshot in seconds
        #Default: 0 (1 Count)
        #Default: 1 (>1 Count)
        [uint32]$Delay = 0,

        #Total number of Screenshots to capture
        #Default = 1
        [uint32]$Count = 1,

        #Additionally copies the Screenshot to the Clipboard
        [System.Management.Automation.SwitchParameter]$Clipboard = $false,

        #Screenshot of the Primary Display only
        [System.Management.Automation.SwitchParameter]$Primary = $false
    )
    begin {
        #=================================================
        # Gather
        #=================================================
        $GetCommandNoun = Get-Command -Name Get-ScreenPNG | Select-Object -ExpandProperty Noun
        $GetCommandVersion = Get-Command -Name Get-ScreenPNG | Select-Object -ExpandProperty Version
        $GetCommandHelpUri = Get-Command -Name Get-ScreenPNG | Select-Object -ExpandProperty HelpUri
        $GetCommandModule = Get-Command -Name Get-ScreenPNG | Select-Object -ExpandProperty Module
        $GetModuleDescription = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty Description
        $GetModuleProjectUri = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty ProjectUri
        $GetModulePath = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty Path
        $MyPictures = (New-Object -ComObject Shell.Application).NameSpace('shell:My Pictures').Self.Path
        #=================================================
        # Adjust Delay
        #=================================================
        if ($Count -gt '1') {if ($Delay -eq 0) {$Delay = 1}}
        #=================================================
        # Determine Task Sequence
        #=================================================
        $LogPath = ''
        $SMSTSLogPath = ''
        try {
            $TSEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction SilentlyContinue
            $IsTaskSequence = $true
            $LogPath = $TSEnv.Value('LogPath')
            $SMSTSLogPath = $TSEnv.Value('_SMSTSLogPath')
        }
        catch [System.Exception] {
            $IsTaskSequence = $false
            $LogPath = ''
            $SMSTSLogPath = ''
        }
        #=================================================
        # Set AutoPath
        #=================================================
        if ($Directory -eq '') {
            if ($IsTaskSequence -and (Test-Path $LogPath)) {
                $AutoPath = Join-Path -Path $LogPath -ChildPath "Screenshots"
            } elseif ($IsTaskSequence -and (Test-Path $SMSTSLogPath)) {
                $AutoPath = Join-Path -Path $SMSTSLogPath -ChildPath "Screenshots"
            } elseif ($env:SystemDrive -eq 'X:') {
                $AutoPath = 'X:\Screenshots'
            } elseif (Test-Path $MyPictures) {
                $AutoPath = Join-Path -Path $MyPictures -ChildPath "Screenshots"
            } else {
                $AutoPath = "$Env:TEMP\Screenshots"
            }
        } else {
            $AutoPath = $Directory
        }
        #=================================================
        # Usage
        #=================================================
        Write-Verbose '======================================================================================================'
        Write-Verbose "$GetCommandNoun $GetCommandVersion $GetCommandHelpUri"
        Write-Verbose $GetModuleDescription
        Write-Verbose "Module Path: $GetModulePath"
        Write-Verbose '======================================================================================================'
        Write-Verbose 'Get-ScreenPNG [[-Directory] <String>] [[-Prefix] <String>] [[-Delay] <UInt32>] [[-Count] <UInt32>] [-Clipboard] [-Primary]'
        Write-Verbose ''
        Write-Verbose '-Directory Directory where the Screenshots will be saved'
        Write-Verbose ' If this value is not set, Path will be automatically set between the following:'
        Write-Verbose ' Defaults = [LogPath\Screenshots] [_SMSTSLogPath\Screenshots] [My Pictures\Screenshots] [$Env:TEMP\Screenshots]'
        Write-Verbose " Value = $AutoPath"
        Write-Verbose ''
        $DateString = (Get-Date).ToString('yyyyMMdd_HHmmss')
        Write-Verbose "-Prefix Pattern in the file name $($Prefix)_$($DateString).png"
        Write-Verbose " Default = Screenshot"
        Write-Verbose " Value = $Prefix"
        Write-Verbose ''
        Write-Verbose '-Count Total number of Screenshots to capture'
        Write-Verbose ' Default = 1'
        Write-Verbose " Value = $Count"
        Write-Verbose ''
        Write-Verbose '-Delay Delay before capturing the Screenshots in seconds'
        Write-Verbose ' Default = 0 (Count = 1) | Default = 1 (Count > 1)'
        Write-Verbose " Value = $Delay"
        Write-Verbose ''
        Write-Verbose '-Clipboard Additionally copies the Screenshot to the Clipboard'
        Write-Verbose " Value = $Clipboard"
        Write-Verbose ''
        Write-Verbose '-Primary Captures Screenshot from the Primary Display only for Multiple Displays'
        Write-Verbose " Value = $Primary"
        Write-Verbose '======================================================================================================'
        #=================================================
        # Load Assemblies
        #=================================================
        Add-Type -Assembly System.Drawing
        Add-Type -Assembly System.Windows.Forms
        #=================================================
    }
    process {
        foreach ($i in 1..$Count) {
            #=================================================
            # Determine Task Sequence (Process Block)
            #=================================================
            $LogPath = ''
            $SMSTSLogPath = ''
            try {
                $TSEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction SilentlyContinue
                $IsTaskSequence = $true
                $LogPath = $TSEnv.Value('LogPath')
                $SMSTSLogPath = $TSEnv.Value('_SMSTSLogPath')
            }
            catch [System.Exception] {
                $IsTaskSequence = $false
                $LogPath = ''
                $SMSTSLogPath = ''
            }
            #=================================================
            # Set AutoPath (Process Block)
            #=================================================
            $AutoPathBackup = $AutoPath
            if ($Directory -eq '') {
                if ($IsTaskSequence -and (Test-Path $LogPath)) {
                    $AutoPath = Join-Path -Path $LogPath -ChildPath "Screenshots"
                } elseif ($IsTaskSequence -and (Test-Path $SMSTSLogPath)) {
                    $AutoPath = Join-Path -Path $SMSTSLogPath -ChildPath "Screenshots"
                } elseif ($env:SystemDrive -eq 'X:') {
                    $AutoPath = 'X:\Screenshots'
                } elseif (Test-Path $MyPictures) {
                    $AutoPath = Join-Path -Path $MyPictures -ChildPath "Screenshots"
                } else {
                    $AutoPath = "$Env:TEMP\Screenshots"
                }
            } else {
                $AutoPath = $Directory
            }
            Write-Verbose "AutoPath is set to $AutoPath"
            #=================================================
            # AutoPathBackup
            #=================================================
            if ($AutoPathBackup -ne $AutoPath) {
                #Path changed, so need to move the content from the previous AutoPath
            }
            #=================================================
            # Determine AutoPath
            #=================================================
            if (!(Test-Path "$AutoPath")) {
                Write-Verbose "Creating snaScreenshot directory at $AutoPath"
                New-Item -Path "$AutoPath" -ItemType Directory -Force -ErrorAction Stop | Out-Null
            }
            #=================================================
            # Delay
            #=================================================
            Write-Verbose "Delay $Delay Seconds"
            Start-Sleep -Seconds $Delay
            #=================================================
            # Display Information
            #=================================================
            $GetDisplayAllScreens = @(Get-DisplayAllScreens)
            $GetDisplayVirtualScreen = Get-DisplayVirtualScreen
            #=================================================
            # Display Number
            #=================================================
            foreach ($Device in $GetDisplayAllScreens) {
                #DateString
                $DateString = (Get-Date).ToString('yyyyMMdd_HHmmss')
                
                #DisplayNumber
                $DisplayNumber = $Device.DeviceName -Replace "[^0-9]"
                Write-Verbose "DisplayNumber: $DisplayNumber"

                #FileName
                if ($Prefix) {
                    $FileName = "$($Prefix)_$($DateString)"
                } else {
                    $FileName = "$($DateString)"
                }

                if ($GetDisplayAllScreens.Count -eq 1) {
                    $FileName = "$($FileName).png"
                } else {
                    $FileName = "$($FileName)_$($DisplayNumber).png"
                }

                if ($Device.Primary -eq $true) {
                    $GetDisplayPrimaryBitmapSize = Get-DisplayPrimaryBitmapSize
                    #Write-Verbose "Width: $($GetDisplayPrimaryBitmapSize.Width)" -Verbose
                    #Write-Verbose "Height: $($GetDisplayPrimaryBitmapSize.Height)" -Verbose
                    $ScreenshotBitmap = New-Object System.Drawing.Bitmap $GetDisplayPrimaryBitmapSize.Width, $GetDisplayPrimaryBitmapSize.Height
                    $ScreenshotGraphics = [System.Drawing.Graphics]::FromImage($ScreenShotBitmap)
                    #Write-Verbose "X: $($GetDisplayVirtualScreen.X)" -Verbose
                    #Write-Verbose "Y: $($GetDisplayVirtualScreen.Y)" -Verbose
                    #Write-Verbose "Size: $($GetDisplayVirtualScreen.Size)" -Verbose
                    $ScreenshotGraphics.CopyFromScreen($GetDisplayVirtualScreen.X, $GetDisplayVirtualScreen.Y, $GetDisplayVirtualScreen.X, $GetDisplayVirtualScreen.Y, $GetDisplayVirtualScreen.Size)
                    Write-Verbose "Saving Primary Screenshot $i of $Count to to $AutoPath\$FileName"
                }
                
                if ($Device.Primary -eq $false) {
                    if ($Primary -eq $true) {Continue}
                    Write-Verbose "Width: $($Device.Bounds.Width)" -Verbose
                    Write-Verbose "Height: $($Device.Bounds.Height)" -Verbose
                    $ScreenshotBitmap = New-Object System.Drawing.Bitmap $Device.Bounds.Width, $Device.Bounds.Height
                    $ScreenshotGraphics = [System.Drawing.Graphics]::FromImage($ScreenShotBitmap)
                    Write-Verbose "X: $($Device.Bounds.X)" -Verbose
                    Write-Verbose "Y: $($Device.Bounds.Y)" -Verbose
                    Write-Verbose "Size: $($GetDisplayVirtualScreen.Size)" -Verbose
                    $ScreenshotGraphics.CopyFromScreen($Device.Bounds.X, $Device.Bounds.Y, 0, 0, $GetDisplayVirtualScreen.Size)
                    Write-Verbose "Saving Secondary Screenshot $i of $Count to to $AutoPath\$FileName"
                }

                #=================================================
                # Save the Screenshot to File
                # https://docs.microsoft.com/en-us/dotnet/api/system.drawing.image.tag?view=dotnet-plat-ext-5.0
                #=================================================
                $ScreenshotBitmap.Save("$AutoPath\$FileName")

                #=================================================
                # Copy the Screenshot to the Clipboard
                # https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.setimage?view=net-5.0
                #=================================================
                if ($Device.Primary -eq $true) {
                    if ($Clipboard) {
                        Write-Verbose "Copying Screenshot to the Clipboard"
                        #Add-Type -Assembly System.Drawing
                        #Add-Type -Assembly System.Windows.Forms
                        [System.Windows.Forms.Clipboard]::SetImage($ScreenshotBitmap)
                    }
                }
            }
            #=================================================
            # Close
            #=================================================
            $ScreenshotGraphics.Dispose()
            $ScreenshotBitmap.Dispose()
            #=================================================
            # Return Get-Item
            #=================================================
            Get-Item "$AutoPath\$FileName"
            #=================================================
        }
    }
    end {}
}
function Set-ClipboardScreenshot {
    <#
    .SYNOPSIS
    Captures a screenshot and copies it to the clipboard
 
    .DESCRIPTION
    Takes a screenshot of the current display(s) and copies the image to the clipboard using Windows Forms bitmap functionality.
 
    .EXAMPLE
    Set-ClipboardScreenshot
    Captures the current screen and copies to clipboard
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-10 - Added comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param ()

    #=================================================
    # Load Assemblies
    #=================================================
    Add-Type -Assembly System.Drawing
    Add-Type -Assembly System.Windows.Forms
    #=================================================
    # Display Information
    #=================================================
    $GetDisplayVirtualScreen = Get-DisplayVirtualScreen
    #=================================================
    # Display Number
    #=================================================
    $GetDisplayPrimaryBitmapSize = Get-DisplayPrimaryBitmapSize
    #Write-Verbose "Width: $($GetDisplayPrimaryBitmapSize.Width)" -Verbose
    #Write-Verbose "Height: $($GetDisplayPrimaryBitmapSize.Height)" -Verbose
    $ScreenShotBitmap = New-Object System.Drawing.Bitmap $GetDisplayPrimaryBitmapSize.Width, $GetDisplayPrimaryBitmapSize.Height
    $ScreenShotGraphics = [System.Drawing.Graphics]::FromImage($ScreenShotBitmap)
    #Write-Verbose "X: $($GetDisplayVirtualScreen.X)" -Verbose
    #Write-Verbose "Y: $($GetDisplayVirtualScreen.Y)" -Verbose
    #Write-Verbose "Size: $($GetDisplayVirtualScreen.Size)" -Verbose
    $ScreenShotGraphics.CopyFromScreen($GetDisplayVirtualScreen.X, $GetDisplayVirtualScreen.Y, $GetDisplayVirtualScreen.X, $GetDisplayVirtualScreen.Y, $GetDisplayVirtualScreen.Size)
    #=================================================
    # Copy the ScreenShot to the Clipboard
    # https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.setimage?view=net-5.0
    #=================================================
    [System.Windows.Forms.Clipboard]::SetImage($ScreenShotBitmap)
    Return Get-Clipboard -Format Image
}
function Start-ScreenPNGProcess {
    <#
    .SYNOPSIS
    Starts a background process to capture screenshots
 
    .DESCRIPTION
    Launches a hidden PowerShell process that periodically captures screenshots and saves them to the specified directory.
 
    .PARAMETER Directory
    Directory where screenshots will be saved. This parameter is mandatory.
 
    .PARAMETER Count
    Total number of screenshots to capture. Default is 9999
 
    .PARAMETER Delay
    Delay in seconds between screenshots. Default is 2 seconds
 
    .EXAMPLE
    Start-ScreenPNGProcess -Directory 'C:\Screenshots'
    Starts capturing screenshots with default delay and count
 
    .EXAMPLE
    Start-ScreenPNGProcess -Directory 'C:\Screenshots' -Count 5 -Delay 3
    Starts capturing 5 screenshots with 3-second intervals
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-10 - Added comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory,

        [uint32]$Delay = 2,

        [uint32]$Count = 9999
    )

    $StartInfo = new-object System.Diagnostics.ProcessStartInfo
    $StartInfo.FileName = 'powershell.exe'
    $StartInfo.Arguments = "-NoExit -WindowStyle Hidden -Command Get-ScreenPNG -Directory $Directory -Count $Count -Delay $Delay"
    $Global:ScreenPNGPath = $Directory
    $Global:ScreenPNGProcess = ([System.Diagnostics.Process]::Start($StartInfo)).Id
}
function Stop-ScreenPNGProcess {
    <#
    .SYNOPSIS
    Stops the background screenshot capture process
 
    .DESCRIPTION
    Terminates the background PowerShell process that is capturing screenshots and clears related global variables.
 
    .EXAMPLE
    Stop-ScreenPNGProcess
    Stops the background screenshot process
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-10 - Added comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param ()

    Stop-Process -Id $Global:ScreenPNGProcess -Force -ErrorAction SilentlyContinue
    $Global:ScreenPNGPath = $null
    $Global:ScreenPNGProcess = $null
}