Screenshot.psm1

# This is a locally sourced Imports file for local development.
# It can be imported by the psm1 in local development to add script level variables.
# It will merged in the build process. This is for local development only.

# region script variables
# $script:resourcePath = "$PSScriptRoot\Resources"


function Get-WindowLocation {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0, Mandatory = $true)]
        [string]$WindowTitle
    )

    Add-Type @"
        using System;
        using System.Runtime.InteropServices;
 
        namespace Screenshot {
 
            public class Window {
                [DllImport("user32.dll")]
                [return: MarshalAs(UnmanagedType.Bool)]
                public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
 
                // From an unknown user on Reddit: https://www.reddit.com/r/PowerShell/comments/2llb4u/comment/cm0e8fi/
                private class unmanaged {
                    // FindWindowByCaption
                    [DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)]
                    internal static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
 
                }
 
                // FindWindowByCaption
                public static IntPtr FindWindowByCaption(string Title) {
                    return unmanaged.FindWindowByCaption(IntPtr.Zero, Title);
                }
            }
 
            public struct RECT {
                public int Left; // x position of upper-left corner
                public int Top; // y position of upper-left corner
                public int Right; // x position of lower-right corner
                public int Bottom; // y position of lower-right corner
            }
        }
"@


    $ProcessHandle = [Screenshot.Window]::FindWindowByCaption($WindowTitle)
    $Rectangle = [Screenshot.RECT]::New()
    if ([Screenshot.Window]::GetWindowRect($ProcessHandle, [ref]$Rectangle)) {
        return $Rectangle
    } else {
        throw "Failed to get window coordinates for '$WindowTitle'."
    }

}

function New-Screenshot {
    <#
.EXTERNALHELP Screenshot-help.xml
#>

    [CmdletBinding(DefaultParameterSetName = 'Coordinates', SupportsShouldProcess = $true)]
    param(
        [Parameter(ParameterSetName = 'Coordinates', Position = 0)]
        [Parameter(ParameterSetName = 'CoordinatesWithSize', Position = 0)]
        [int]$X = 0,
        [Parameter(ParameterSetName = 'Coordinates', Position = 1)]
        [Parameter(ParameterSetName = 'CoordinatesWithSize', Position = 1)]
        [int]$Y = 0,
        [Parameter(ParameterSetName = 'Coordinates', Position = 2)]
        [int]$Width,
        [Parameter(ParameterSetName = 'Coordinates', Position = 3)]
        [int]$Height,

        [Parameter(ParameterSetName = 'WindowTitle', Position = 0)]
        [string]$WindowTitle,

        [Parameter(ParameterSetName = 'WindowTitle', Position = 1)]
        [Parameter(ParameterSetName = 'Coordinates', Position = 4)]
        [Parameter(ParameterSetName = 'CoordinatesWithSize', Position = 2)]
        [string]$Path,

        [Parameter(ParameterSetName = 'WindowTitle', Position = 2)]
        [Parameter(ParameterSetName = 'Coordinates', Position = 5)]
        [Parameter(ParameterSetName = 'CoordinatesWithSize', Position = 3)]
        [string]$FileName = "Screenshot_{0}.jpg" -f (Get-Date -Format "yyyyMMdd_HHmmss")
    )

    Add-Type -AssemblyName System.Windows.Forms, System.Drawing

    switch ($PSCmdlet.ParameterSetName) {
        "Coordinates" {
            if ($Width -eq 0) {
                $Width = [System.Windows.Forms.SystemInformation]::VirtualScreen.Width
                if ($X -gt 0) {
                    $Width = $Width - $X
                }
            }
            if ($Height -eq 0) {
                $Height = [System.Windows.Forms.SystemInformation]::VirtualScreen.Height
                if ($Y -gt 0) {
                    $Height = $Height - $Y
                }
            }
            break
        }
        "WindowTitle" {
            $Coordinates = Get-WindowLocation -WindowTitle $WindowTitle
            $X = $Coordinates.Left
            $Y = $Coordinates.Top
            $Width = $Coordinates.Right - $Coordinates.Left
            $Height = $Coordinates.Bottom - $Coordinates.Top
            break
        }
    }

    $Bitmap = [System.Drawing.Bitmap]::new($Width, $Height)
    $Graphic = [System.Drawing.Graphics]::FromImage($bitmap)
    $Graphic.CopyFromScreen($X, $Y, 0, 0, [System.Drawing.Size]::new($Width, $Height))

    #region Screenshot Destination Path
    if([string]::IsNullOrEmpty($Path)) {
        $Path = Get-Location
        # Fallback to TEMP if current location is root of C:\ and user is not admin to avoid "Access Denied" error
        # not really necessary, but the previous behavior annoyed me lol
        if( $Path.ToString() -eq "C:\" -and
            ([Security.Principal.WindowsIdentity]::GetCurrent().Groups -notcontains 'S-1-5-32-544')) {
                $Path = $env:TEMP
        }
    }
    $DestinationPath = Join-Path -Path $Path -ChildPath $FileName
    #endregion Screenshot Destination Path

    #region Save Screenshot
    if ($PSCmdlet.ShouldProcess($DestinationPath, ("Saving Screenshot to path: '{0}'" -f $DestinationPath))) {
        try {
            $Bitmap.Save($DestinationPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
            Get-Item -Path $DestinationPath
        } catch {
            throw "Failed to save screenshot to path: '{0}'. Error: {1}" -f $DestinationPath, $_.Exception.Message
        }
    }
    #endregion Save Screenshot
}