[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: 4195
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 the Batch window. It supports scripts running in a console window or in Windows Terminal.
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% "screenshots"
pause
exit /b

:init_captcon
:: - BRIEF -
::  Take a screenshot of the visible part of the console or terminal window. Automatically save it as JPEG file.
::    The format of the file name will be "captcon_yyyyMMdd_HHmmss.ssssss.jpg".
:: - SYNTAX -
::  %captcon% ["path"]
::    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%
::  Take a screenshot and save it on the user's desktop:
::    %captcon% "%userprofile%\Desktop"
setlocal DisableDelayedExpansion
set captcon=for /l %%. in (1 1 2) do if %%.==2 (^
%=% set "arg=^^!arg:`=``^^!"^&set "arg=^^!arg:{=`{^^!"^&^
%=% for /f "tokens=*" %%/ in ("^^!arg:}=`}^^!") do endlocal^&powershell.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 dwAttribute,int[] pvAttribute,int cbAttribute);^
%===% [DllImport(\"kernel32.dll\")] public static extern IntPtr GetConsoleWindow();^
%===% [DllImport(\"user32.dll\")] public static extern void GetMonitorInfoW(IntPtr hMonitor,int[] lpmi);^
%===% [DllImport(\"user32.dll\")] public static extern void GetWindowThreadProcessId(IntPtr hWnd,ref int lpdwProcessId);^
%===% [DllImport(\"user32.dll\")] public static extern IntPtr MonitorFromWindow(IntPtr hwnd,int dwFlags);^
%===% [DllImport(\"user32.dll\")] public static extern IntPtr SendMessageW(IntPtr hWnd,int Msg,IntPtr wParam,IntPtr lParam);^
%===% [DllImport(\"user32.dll\")] public static extern void SetProcessDPIAware();^
%===% [DllImport(\"shcore.dll\")] public static extern void SetProcessDpiAwareness(int value);';^
%=% try{$w::SetProcessDpiAwareness(2)}catch{$w::SetProcessDPIAware()}^
%=% $hwnd=$w::GetConsoleWindow();^
%=% if($w::SendMessageW($hwnd,0x7F,[IntPtr]::Zero,[IntPtr]::Zero) -eq [IntPtr]::Zero){^
%===% $pidc=0;^
%===% $w::GetWindowThreadProcessId($hwnd,[ref]$pidc);^
%===% $hwnd=(gps -id (gwmi Win32_Process -filter ProcessId=$pidc).ParentProcessId).MainWindowHandle;}^
%=% [int[]]$rect=0,0,0,0; [int[]]$moninf=40,0,0,0,0,0,0,0,0,0;^
%=% $w::DwmGetWindowAttribute($hwnd,9,$rect,16);^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd,2),$moninf);^
%=% $rect=[Math]::Max($rect[0],$moninf[5]),[Math]::Max($rect[1],$moninf[6]),[Math]::Min($rect[2],$moninf[7]),[Math]::Min($rect[3],$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((\"%%~/\" -ne \"`=``\") -and \"%%~/\"){\"%%~/\"}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
*** Magic Numbers ***
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$w::SetProcessDpiAwareness(2)
2 is the value of PROCESS_PER_MONITOR_DPI_AWARE. (Not supported before Win 8.1.)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$w::SendMessageW($hwnd,0x7F,[IntPtr]::Zero,[IntPtr]::Zero)
0x7F is the value of WM_GETICON. If we fail to get the icon handle of the console window, we will assume the script is running in Windows Terminal.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[int[]]$rect=0,0,0,0
The zeros are there to initialize an array of exactly 4 integers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[int[]]$moninf=40,0,0,0,0,0,0,0,0,0
40 is the size of the $moninf array in bytes. The zeros are there to make sure the array consists of exactly 10 integers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$w::DwmGetWindowAttribute($hwnd,9,$rect,16)
9 is the value of DWMWA_EXTENDED_FRAME_BOUNDS. 16 is the size of the $rect array in bytes.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$w::MonitorFromWindow($hwnd,2)
2 is the value of MONITOR_DEFAULTTONEAREST
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Drawing.Imaging.EncoderParameter([Drawing.Imaging.Encoder]::Quality,90)
90 is for 90% of the original image quality. So, a certain compression rate is applied to shrink the file size.
Last edited by aGerman on 16 Jul 2021 09:10, edited 3 times in total.

IcarusLives
Posts: 127
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: 4195
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: 176
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: 4195
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