[How-To] Screenshot of Batch window (PowerShell hybrid)

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

[How-To] Screenshot of Batch window (PowerShell hybrid)

#1 Post by aGerman » 11 Jul 2021 16:39

The code below contains the captcon macro, which is initialized by calling the :init_captcon subroutine. It takes a screenshot of the visible part of a window.
The captured screenshot is automatically saved as JPEG file. The file name contains a time stamp and is based on template "captcon_yyyyMMdd_HHmmss.ssssss.jpg". Microseconds are used to create sufficiently unique file names.
Optionally, the destination folder for the screenshot can be passed as argument.

Steffen

Code: Select all

@echo off &setlocal
call :init_captcon
echo This is a test.
2>nul md "screenshots"
%captcon% 0 "screenshots"
pause
exit /b

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init_captcon
setlocal DisableDelayedExpansion
:: prefer PowerShell Core if installed
for %%i in ("pwsh.exe") do if "%%~$PATH:i"=="" (set "ps=powershell") else set "ps=pwsh"

:: - BRIEF -
::  Take a screenshot of the visible part of a window. Automatically save it as JPEG file.
::    The format of the file name will be "captcon_yyyyMMdd_HHmmss.ssssss.jpg".
::    NOTE: The window to be captured must be the foreground window. Otherwise the macro will fail.
:: - SYNTAX -
::  %captcon% pid ["path"]
::    pid    ID of a process whose main window is to be captured
::            Pass 0 to take a screenshot of the current console window
::    path   (optional) Folder where the screenshot is saved. If not specified, the current workdir will be used.
:: - EXAMPLES -
::  Take a screenshot and save it in the current workdir:
::    %captcon% 0
::  Take a screenshot and save it on the user's desktop:
::    %captcon% 0 "%userprofile%\Desktop"
set captcon=for %%# in (1 2) do if %%#==2 (^
%=% set "arg=^^!arg:`=``^^!"^&set "arg=^^!arg:{=`{^^!"^&^
%=% for /f "tokens=1*" %%. in ("^^!arg:}=`}^^!") do endlocal^&^
%=% %ps%.exe -nop -ep Bypass -c ^"^
%===% Add-Type -an System.Windows.Forms; Add-Type -an System.Drawing;^
%===% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"dwmapi.dll\")]^
%=======% public static extern void DwmGetWindowAttribute(IntPtr hwnd, int attribId, int[] attribVal, int attribLen);^
%=====% [DllImport(\"kernel32.dll\")]^
%=======% public static extern IntPtr GetConsoleWindow();^
%=====% [DllImport(\"user32.dll\")]^
%=======% public static extern IntPtr GetForegroundWindow();^
%=====% [DllImport(\"user32.dll\")]^
%=======% public static extern void GetMonitorInfoW(IntPtr hMon, int[] monInf);^
%=====% [DllImport(\"user32.dll\")]^
%=======% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int flags);^
%=====% [DllImport(\"user32.dll\")]^
%=======% public static extern void SetProcessDPIAware();^
%=====% [DllImport(\"shcore.dll\")]^
%=======% public static extern void SetProcessDpiAwareness(int value);^
%===% ';^
%===% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%===% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE)} catch {$w::SetProcessDPIAware()}^
%===% $wpid=0;^
%===% if (-not [Int32]::TryParse('%%~.', [ref]$wpid)) {exit 1}^
%===% if ($wpid -gt 0) {^
%=====% $hwnd=(gps -id $wpid -ea SilentlyContinue).MainWindowHandle;^
%===% } else {^
%=====% $hwnd=$w::GetConsoleWindow();^
%===% }^
%===% if ($hwnd -ne $w::GetForegroundWindow()) {exit 1}^
%===   The $rect array is a replacement for the RECT structure. The elements at index   =% ^
%===   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.  =% ^
%===% $rect=[int[]]::new(4);^
%===% $DWMWA_EXTENDED_FRAME_BOUNDS=9;^
%===% $w::DwmGetWindowAttribute($hwnd, $DWMWA_EXTENDED_FRAME_BOUNDS, $rect, 16);^
%===   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%===   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%===% $moninf=[int[]]::new(10);^
%===% $moninf[0]=40;^
%===% $MONITOR_DEFAULTTONEAREST=2;^
%===% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST), $moninf);^
%===% $rect=[Math]::Max($rect[0] + 2, $moninf[5]),^
%=========% [Math]::Max($rect[1], $moninf[6]),^
%=========% [Math]::Min($rect[2] - 2, $moninf[7]),^
%=========% [Math]::Min($rect[3] - 2, $moninf[8]);^
%===% $width=$rect[2] - $rect[0];^
%===% $height=$rect[3] - $rect[1];^
%===% $bm=New-Object Drawing.Bitmap($width, $height);^
%===% [Drawing.Graphics]::FromImage($bm).CopyFromScreen($rect[0], $rect[1], 0, 0, \"$width,$height\");^
%===% $name=Join-Path $(if(\"%%~/\") {\"%%~/\".TrimEnd()} else {'.'})^
%===================% $('captcon_' + (Get-Date -f yyyyMMdd_HHmmss.ffffff) + '.jpg');^
%===% $codecInf=[Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()^^^^^^^|?{$_.FormatDescription -eq 'JPEG'};^
%===% $encParams=New-Object Drawing.Imaging.EncoderParameters(1);^
%===% $encParams.Param[0]=New-Object Drawing.Imaging.EncoderParameter([Drawing.Imaging.Encoder]::Quality, 90);^
%===% $bm.Save($name, $codecInf, $encParams);^
%===% $encParams.Dispose();^
%===% $bm.Dispose();^
%=% ^") else setlocal EnableDelayedExpansion^&set arg=

endlocal &set "captcon=%captcon%"
if !!# neq # set "captcon=%captcon:^^!=!%"
exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Pass 0 as process ID to capture the console window. Another window can be captured by passing the ID of the belonging process as long as it is the foreground window. Related:
viewtopic.php?f=3&t=9811

In case of the Windows Terminal use the %TermPid% macro. Refer to:
viewtopic.php?f=3&t=10576
Last edited by aGerman on 29 Oct 2022 14:18, edited 4 times in total.
Reason: support for other windows, such like Windows Terminal

IcarusLives
Posts: 161
Joined: 17 Jan 2016 23:55

Re: [How-To] Screenshot of Batch window (PowerShell hybrid)

#2 Post by IcarusLives » 14 Jul 2021 19:51

I love it! Works great on my end. Can be used for so many interesting things such as monitoring software. Well done, thanks for the release.

aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Re: [How-To] Screenshot of Batch window (PowerShell hybrid)

#3 Post by aGerman » 15 Jul 2021 07:28

Thanks for your feedback!
I'm not sure if the macro is particularly useful. Likely only for a few people. However, it shows how to take a screenshot from within a script, which can easily be reused for similar macro codes. All you need are virtual screen coordinates of the rectangle you want to capture. The only thing which might not be obvious in my code is that I used a trick to represent data as arrays rather than structure types in the Windows API. Defining those structures would have made the macro unnecessarily long. So, in terms of the original RECT structure, my $rect[0] is for left, $rect[1] for top, $rect[2] for right, and $rect[3] for bottom. (Same for $moninf[5]..$moninf[8] which represent left..bottom bounds of the monitor's work area.)

Steffen

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: [How-To] Screenshot of Batch window (PowerShell hybrid)

#4 Post by jfl » 16 Jul 2021 10:14

FYI, in my Systems Tools Library, there's a Get-Console.ps1 script which captures the console window, and puts it into the clipboard.

By default, it captures it as HTML with colors. This makes it easy to capture a command output, and paste it into an email or post it on a server. Also the fact that it's HTML and not an image allows the author to do manual corrections like removing unwanted lines, or hiding confidential information; And the readers can copy the commands, and paste them into their own shells.

And there's a batch front-end to the above PowerShell script, for use in cmd.exe shells: Get-Console.bat

Sample output
(Notice how you can copy text from that batch screen capture!)

aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Re: [How-To] Screenshot of Batch window (PowerShell hybrid)

#5 Post by aGerman » 16 Jul 2021 10:24

jfl wrote:
16 Jul 2021 10:14
it captures it as HTML with colors
That's awesome 😮 Thanks for sharing Jean-François!

Steffen

Post Reply