Skip to content

Instantly share code, notes, and snippets.

@DustinVenegas
Created March 3, 2022 07:19
Show Gist options
  • Save DustinVenegas/6c3e818e6e2882784d162246bb996f46 to your computer and use it in GitHub Desktop.
Save DustinVenegas/6c3e818e6e2882784d162246bb996f46 to your computer and use it in GitHub Desktop.
Tweet a Screen Shot with PowerShell Core and Windows 10
#Requires -PSEdition Core
#Requires -Version 7.0
#Requires -Assembly System.Drawing.Common
function Invoke-MainWindowScreenCapture {
<#
.SYNOPSIS
Captures the screen of the main window for a given process on Windows.
.DESCRIPTION
Takes a screen shot of the main window for a given process on Microsoft
Windows. Only Windows is supported. See note about
.PARAMETER Process
Process to screen capture. Defaults to the current PowerShell process.
.NOTES
Works well as a low-Dependency screen capture tool. Otherwise, consider a a tool like KSnip.
Only works on PowerShell Core with Windows.
-------------------------------------------
CopyFromScreen is available on Microsoft Windows with .NET Core (or Full) via the System.Drawing.Common
library. A partial port of System.Drawing.Common exists for Linux, but the CopyFromScreen function was
not ported, like many methods.
Incorrectly identified as Windows Defender Threat.
--------------------------------------------------
Windows Defender incorrectly identifies this script as a threat named "HackTool:PowerShell/EmptireGetScreenshot.C".
This script will be quarantined upon first execution. Either allow the script through Windows Defender or consider
using a different tool to take screen shots.
#>
[CmdletBinding()]
[Alias("screenshot")]
param(
[Diagnostics.Process]$Process = [Diagnostics.Process]::GetCurrentProcess()
)
begin {
function Get-ParentWindowHandle {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True, ValuefromPipeline=$True)]
[Diagnostics.Process]$Process
)
process {
$p = $Process
$mwh = $p.MainWindowHandle
while ($p) {
if (0 -lt $p.MainWindowHandle) {
Write-Verbose "Found MainWindowHandle on process $p, $($p.ID)"
$mwh = $p.MainWindowHandle
break
}
$p = $p.Parent
}
Write-Verbose -Message "Main window handle: $mwh"
if (0 -lt $mwh) {
Write-Output $mwh
} else {
Write-Error "Could not determine parent window handle from process: $p"
}
}
}
function Get-WindowRectangle {
[CmdletBinding()]
param (
[Parameter(Mandatory=$True, ValuefromPipeline=$True)]
[IntPtr]$MainWindowHandle
)
begin {
# Based on Boe Prox's Window Placment in Windows PowerShell with PInvoke.
# https://devblogs.microsoft.com/scripting/weekend-scripter-manage-window-placement-by-using-pinvoke/
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class WindowsAPI {
[DllImport("user32.dll")]
public static extern int GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE dwAttribute, out RECT pvAttribute, int cbAttribute);
}
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
}
public enum DWMWINDOWATTRIBUTE : uint
{
NCRenderingEnabled = 1,
NCRenderingPolicy,
TransitionsForceDisabled,
AllowNCPaint,
CaptionButtonBounds,
NonClientRtlLayout,
ForceIconicRepresentation,
Flip3DPolicy,
ExtendedFrameBounds,
HasIconicBitmap,
DisallowPeek,
ExcludedFromPeek,
Cloak,
Cloaked,
FreezeRepresentation
}
"@
}
process {
$rect = New-Object RECT
$frame = New-Object RECT
if ([WindowsAPI]::GetWindowRect($MainWindowHandle, [ref] $rect)) {
# Take Windows Aero borders into account by obtaining the Window rectangle frame too.
# https://stackoverflow.com/questions/34139450/getwindowrect-returns-a-size-including-invisible-borders
if ([WindowsAPI]::DwmGetWindowAttribute($MainWindowHandle, [DWMWINDOWATTRIBUTE]::ExtendedFrameBounds, [ref]$frame, [Runtime.InteropServices.Marshal]::SizeOf($rect))) {
Write-Error -Message "Call to DwmGetWindowAttribute failed"
}
$border = [Drawing.Rectangle]::FromLTRB($frame.Left-$rect.Left, $frame.Top-$rect.Top, $frame.Right-$rect.Right, $frame.Left-$rect.Left)
Write-Verbose "rect is: $rect, frame is: $frame, border is: $border"
return [Drawing.Rectangle]::FromLTRB($rect.Left+$border.Left, $rect.Top+$border.Top, $rect.Right+$border.Right, $rect.Bottom-$border.Bottom)
} else {
Write-Error "could not determine window rect from handle $MainWindowHandle"
}
}
}
[Reflection.Assembly]::LoadWithPartialName("System.Drawing.Common") | Out-Null
$graphics = [Drawing.Graphics]$null
$bmp = [Drawing.Bitmap]$null
}
process {
$path = "$HOME\Pictures\PowerShell\psscreencapture-$(([DateTimeOffset](Get-Date)).ToUnixTimeSeconds()).png"
# create a rectangle sized from the main window of the root process
$pwh = Get-ParentWindowHandle -Process $process
$bounds = Get-WindowRectangle -MainWindowHandle $pwh
Write-Verbose "Parent Window Handle: $pwh, Bounds: $bounds"
# Copy from the screen into the rectangle
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
# Write the image
$bmp.Save($path)
# Emit the path written
Write-Output $path
}
end {
if ($graphics) { $graphics.Dispose() | Out-Null }
if ($bmp) { $bmp.dispose | Out-Null }
}
}
#Requires -PSEdition Core
#Requires -Version 7.0
#Requires -Module bluebirdps
# Take screen capture of the current process.
Invoke-MainWindowScreenCapture | Set-Variable mwsc
# Now, Tweet it without checking anything.
Send-TwitterMedia -Path $mwsc -Category TweetImage | Set-Variable tm
Publish-Tweet -TweetText "Oh snap, #PowerShell!" -MediaId $($tm | Select-Object -ExpandProperty media_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment