How to stop a windows 10 machine from sleeping/hibernating down when running a powershell process? - powershell

I have a powershell process that reads records from a remote server and copies them into a local database. When it runs, it might run for 8-12 hours.
How do I prevent the computer from shutting down (or going into sleep/hibernate mode) during this time? I know I can adjust the 'Power and sleep settings' to set the computer to never sleep, but that's not what I'm looking for - I do want it to go to sleep when the process isn't running.
I know that sleep/hibernate is suspended if a netflix or youtube video is running, I'd like the computer to do the same when a powershell process is running.
The powershell process runs in a command window on the desktop - I'm happy for the screen saver to activate, but what I don't want to happen is for me to wake the machine after 8 hours and discover that the process only ran for 10 minutes before the computer went to sleep!

With some extra effort, you can achieve the desired behavior with the standard powercfg.exe utility, by using a custom, always-on power scheme that is created on demand and temporarily activated for the duration of your script run:
Note:
Look for comment # YOUR CODE GOES HERE below.
For a .NET / Windows API-based alternative, see this answer.
# Define the properties of a custom power scheme, to be created on demand.
$schemeGuid = 'e03c2dc5-fac9-4f5d-9948-0a2fb9009d67' # randomly created with New-Guid
$schemeName = 'Always on'
$schemeDescr = 'Custom power scheme to keep the system awake indefinitely.'
# Helper function that ensures that the most recent powercfg.exe call succeeded.
function assert-ok { if ($LASTEXITCODE -ne 0) { throw } }
# Determine the currently active power scheme, so it can be restored at the end.
$prevGuid = (powercfg -getactivescheme) -replace '^.+([-0-9a-f]{36}).+$', '$1'
assert-ok
# Temporarily activate a custom always-on power scheme; create it on demand.
try {
# Try to change to the custom scheme.
powercfg -setactive $schemeGuid 2>$null
if ($LASTEXITCODE -ne 0) { # Changing failed -> create the scheme on demand.
# Clone the 'High performance' scheme.
$null = powercfg -duplicatescheme SCHEME_MIN $schemeGuid
assert-ok
# Change its name and description.
$null = powercfg -changename $schemeGuid $schemeName $schemeDescr
# Activate it
$null = powercfg -setactive $schemeGuid
assert-ok
# Change all settings to be always on.
# Note:
# * Remove 'monitor-timeout-ac', 'monitor-timeout-dc' if it's OK
# for the *display* to go to sleep.
# * If you make changes here, you'll have to run powercfg -delete $schemeGuid
# or delete the 'Always on' scheme via the GUI for changes to take effect.
# * On an AC-only machine (desktop, server) the *-ac settings aren't needed.
$settings = 'monitor-timeout-ac', 'monitor-timeout-dc', 'disk-timeout-ac', 'disk-timeout-dc', 'standby-timeout-ac', 'standby-timeout-dc', 'hibernate-timeout-ac', 'hibernate-timeout-dc'
foreach ($setting in $settings) {
powercfg -change $setting 0 # 0 == Never
assert-ok
}
}
# YOUR CODE GOES HERE.
# In this sample, wait for the user to press Enter before exiting.
# Before that, the 'Always on' power scheme should remain in
# effect, and the machine shouldn't go to sleep.
pause
} finally { # Executes even when the script is aborted with Ctrl-C.
# Reactivate the previously active power scheme.
powercfg -setactive $prevGuid
}
You could create a wrapper script from the above, to which you pass the path of the script to execute.
If you don't mind modifying the currently active scheme, you can use the approach shown in Kerr's answer, using per-setting powercfg -change <setting> <value-in-minutes> calls (/x / -x is an alias of /change / -change), using one of the following <setting> names in each call; passing 0 as <value-in-minutes> represents never:
monitor-timeout-ac
monitor-timeout-dc
disk-timeout-ac
disk-timeout-dc
standby-timeout-ac
standby-timeout-dc
hibernate-timeout-ac
hibernate-timeout-dc
Note, however, that such changes are persistent, so you may want to restore the original values later, which takes extra effort.

To offer a .NET / Windows API-based alternative to the powercfg.exe-based solution:
Note:
The solution uses Add-Type to compile C# code on demand, which incurs a performance penalty the first time the code is called in the current session.
It is important to call ::StayAwake($false) in the same session in order to clear the power requests made.
Look for comment # YOUR CODE GOES HERE below.
This solution was adapted from this C# answer by MarkusEgle.
Add-Type -ErrorAction Stop -Name PowerUtil -Namespace Windows -MemberDefinition #'
// Member variables.
static IntPtr _powerRequest;
static bool _mustResetDisplayRequestToo;
// P/Invoke function declarations.
[DllImport("kernel32.dll")]
static extern IntPtr PowerCreateRequest(ref POWER_REQUEST_CONTEXT Context);
[DllImport("kernel32.dll")]
static extern bool PowerSetRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll")]
static extern bool PowerClearRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
static extern int CloseHandle(IntPtr hObject);
// Availablity Request Enumerations and Constants
enum PowerRequestType
{
PowerRequestDisplayRequired = 0,
PowerRequestSystemRequired,
PowerRequestAwayModeRequired,
PowerRequestMaximum
}
const int POWER_REQUEST_CONTEXT_VERSION = 0;
const int POWER_REQUEST_CONTEXT_SIMPLE_STRING = 0x1;
// Availablity Request Structures
// Note: Windows defines the POWER_REQUEST_CONTEXT structure with an
// internal union of SimpleReasonString and Detailed information.
// To avoid runtime interop issues, this version of
// POWER_REQUEST_CONTEXT only supports SimpleReasonString.
// To use the detailed information,
// define the PowerCreateRequest function with the first
// parameter of type POWER_REQUEST_CONTEXT_DETAILED.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct POWER_REQUEST_CONTEXT
{
public UInt32 Version;
public UInt32 Flags;
[MarshalAs(UnmanagedType.LPWStr)]
public string SimpleReasonString;
}
/// <summary>
/// Prevents the system from going to sleep, by default including the display.
/// </summary>
/// <param name="enable">
/// True to turn on, False to turn off. Passing True must be paired with a later call passing False.
/// If you pass True repeatedly, subsequent invocations take no actions and ignore the parameters.
/// If you pass False, the remaining paramters are ignored.
// If you pass False without having passed True earlier, no action is performed.
//// </param>
/// <param name="includeDisplay">True to also keep the display awake; defaults to True.</param>
/// <param name="reasonString">
/// A string describing why the system is being kept awake; defaults to the current process' command line.
/// This will show in the output from `powercfg -requests` (requires elevation).
/// </param>
public static void StayAwake(bool enable, bool includeDisplay = true, string reasonString = null)
{
if (enable)
{
// Already enabled: quietly do nothing.
if (_powerRequest != IntPtr.Zero) { return; }
// Configure the reason string.
POWER_REQUEST_CONTEXT powerRequestContext;
powerRequestContext.Version = POWER_REQUEST_CONTEXT_VERSION;
powerRequestContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
powerRequestContext.SimpleReasonString = reasonString ?? System.Environment.CommandLine; // The reason for making the power request.
// Create the request (returns a handle).
_powerRequest = PowerCreateRequest(ref powerRequestContext);
// Set the request(s).
PowerSetRequest(_powerRequest, PowerRequestType.PowerRequestSystemRequired);
if (includeDisplay) { PowerSetRequest(_powerRequest, PowerRequestType.PowerRequestDisplayRequired); }
_mustResetDisplayRequestToo = includeDisplay;
}
else
{
// Not previously enabled: quietly do nothing.
if (_powerRequest == IntPtr.Zero) { return; }
// Clear the request
PowerClearRequest(_powerRequest, PowerRequestType.PowerRequestSystemRequired);
if (_mustResetDisplayRequestToo) { PowerClearRequest(_powerRequest, PowerRequestType.PowerRequestDisplayRequired); }
CloseHandle(_powerRequest);
_powerRequest = IntPtr.Zero;
}
}
// Overload that allows passing a reason string while defaulting to keeping the display awake too.
public static void StayAwake(bool enable, string reasonString)
{
StayAwake(enable, false, reasonString);
}
'#
try {
# Create power request(s) that keep the system awake.
# Pass $false as the 2nd argument to allow the display to go to sleep.
# The reason string is visible when you run `powercfg.exe -requests` to show current requests
# (requires elevation).
# Defaults: keep the display awake too, use the current process' command line as the reason string.
[Windows.PowerUtil]::StayAwake($true, $true, "Running long-running script $PSCommandPath.")
# YOUR CODE GOES HERE.
# In this sample, wait for the user to press Enter before exiting.
# Before that, the system should stay awake indefinitely.
pause
} finally { # This ensures that the previous scheme is restored even when the script is aborted with Ctrl-C.
# Clear the power requests.
[Windows.PowerUtil]::StayAwake($false)
}

Simple one-liner that I use:
Powercfg /x -standby-timeout-ac 0

Related

Change and update the size of the cursor in Windows 10 via PowerShell

I've written the code below to affect (what I think) are the only reg keys responsible for the size of the cursor and pointer in Windows 10.
Here's the code I have so far (Some additional comments within):
$RegConnect = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"CurrentUser", "$env:COMPUTERNAME")
$RegCursorsAccess = $RegConnect.OpenSubKey("Software\Microsoft\Accessibility", $true)
$RegCursorsControlPanel = $RegConnect.OpenSubKey("Control Panel\Cursors", $true)
# In the code below I'm trying to change the size of the cursor.
$RegCursorsControlPanel.SetValue("CursorBaseSize", 48)
$RegCursorsAccess.SetValue("CursorSize", 3)
$RegCursorsAccess.Close()
$RegConnect.Close()
# This section is where I thought it would update the cursor size.
# Here is where it lists stuff relating to setting and updating any settings changed.
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
# SPI_SETCURSORS
# 0x0057
# Reloads the system cursors. Set the uiParam parameter to zero and the pvParam parameter to NULL.
$CSharpSig = #'
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(
uint uiAction,
uint uiParam,
uint pvParam,
uint fWinIni);
'#
$CursorRefresh = Add-Type -MemberDefinition $CSharpSig -Name WinAPICall -Namespace SystemParamInfo -PassThru
$CursorRefresh::SystemParametersInfo(0x0057,0,$null,0)
It will change the correct values in the registry.
So if I run this PowerShell code the mouse size in the ease of access setting is at the correct value.
But the cursor doesn't update.
How is it possible to force the update without logging out and back in or restarting the machine.
Here are some related MS links:
WM_SETTINGCHANGE message
SystemParametersInfoA function
EDIT - Some additional info
If I run Process Monitor from Sysinternals and dig deep in there I can see this under the stack summary.
This may lead someone more knowledgeable than me to find how to update the mouse size.
The HKCU\Control Panel\Cursors\(Default) section SettingsHandlers_nt.dll
And this also for the accessibility section. Windows.UI.Accessibility.dll
Here are the settings I used in Process Monitors filter to narrow down the items.
So after a bit of hacking about SystemSettings.exe with cheat engine I found how MS sets the cursor size. It ends up still using SystemParametersInfo but with some undocumented arguments. Try the following :)
SystemParametersInfo(0x2029, 0, 16, 0x01);
to set a cursor size of 16. yup you can go below their minimum of 32, all the way down to 1 :)
But the cursor doesn't update.
After registry value changed, it requires trigger to apply these update.
It can be done using SystemParametersInfo function with SPIF_UPDATEINIFILE and SPIF_SENDCHANGE to writes the new system-wide parameter setting to the user profile and broadcasts the WM_SETTINGCHANGE message after updating the user profile.
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
The following is a PowerShell command example:
$RegConnect = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"CurrentUser","$env:COMPUTERNAME")
$RegCursors = $RegConnect.OpenSubKey("Control Panel\Cursors",$true)
$RegCursors.SetValue("","Windows Black")
$RegCursors.SetValue("CursorBaseSize",0x40)
$RegCursors.SetValue("AppStarting","%SystemRoot%\cursors\wait_r.cur")
$RegCursors.SetValue("Arrow","%SystemRoot%\cursors\arrow_rl.cur")
$RegCursors.SetValue("Crosshair","%SystemRoot%\cursors\cross_r.cur")
$RegCursors.SetValue("Hand","")
$RegCursors.SetValue("Help","%SystemRoot%\cursors\help_r.cur")
$RegCursors.SetValue("IBeam","%SystemRoot%\cursors\beam_r.cur")
$RegCursors.SetValue("No","%SystemRoot%\cursors\no_r.cur")
$RegCursors.SetValue("NWPen","%SystemRoot%\cursors\pen_r.cur")
$RegCursors.SetValue("SizeAll","%SystemRoot%\cursors\move_r.cur")
$RegCursors.SetValue("SizeNESW","%SystemRoot%\cursors\size1_r.cur")
$RegCursors.SetValue("SizeNS","%SystemRoot%\cursors\size4_r.cur")
$RegCursors.SetValue("SizeNWSE","%SystemRoot%\cursors\size2_r.cur")
$RegCursors.SetValue("SizeWE","%SystemRoot%\cursors\size3_r.cur")
$RegCursors.SetValue("UpArrow","%SystemRoot%\cursors\up_r.cur")
$RegCursors.SetValue("Wait","%SystemRoot%\cursors\busy_r.cur")
$RegCursors.Close()
$RegConnect.Close()
function Update-UserPreferencesMask {
$Signature = #"
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
const int SPI_SETCURSORS = 0x0057;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDCHANGE = 0x02;
public static void UpdateUserPreferencesMask() {
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
"#
Add-Type -MemberDefinition $Signature -Name UserPreferencesMaskSPI -Namespace User32
[User32.UserPreferencesMaskSPI]::UpdateUserPreferencesMask()
}
Update-UserPreferencesMask
But unfortunately, the cursor size update doesn't work in this way.
A workaround is using arrow_rl.cur (large image) instead of arrow_r.cur.
Refer to Use PowerShell to Change the Mouse Pointer Scheme, Programmatically change custom mouse cursor in windows.

Can Powershell detect if a specific program is currently playing sound?

I am trying to find a way to detect if one specific program is playing sound. To make a long story short, the computers at my workplace all have a program called ReSoin.exe which should always be playing sound if it is functioning properly. If ReSoin is active but is not playing sound, I need to close and reopen ReSoin. I want to automate this process.
Using the method described by Persistent13 here and my very basic understanding of Powershell, I have created a loop to determine if a Windows machine is playing any sound. If Resoin is active and no sound is playing on the computer, the loop closes and reopens ReSoin.
Add-Type -TypeDefinition #'
using System;
using System.Runtime.InteropServices;
namespace Foo
{
public class Bar
{
public static bool IsWindowsPlayingSound()
{
IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
IMMDevice speakers = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
IAudioMeterInformation meter = (IAudioMeterInformation)speakers.Activate(typeof(IAudioMeterInformation).GUID, 0, IntPtr.Zero);
float value = meter.GetPeakValue();
return value > 1E-08;
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
private class MMDeviceEnumerator
{
}
private enum EDataFlow
{
eRender,
eCapture,
eAll,
}
private enum ERole
{
eConsole,
eMultimedia,
eCommunications,
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
private interface IMMDeviceEnumerator
{
void NotNeeded();
IMMDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role);
// the rest is not defined/needed
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D666063F-1587-4E43-81F1-B948E807363F")]
private interface IMMDevice
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid iid, int dwClsCtx, IntPtr pActivationParams);
// the rest is not defined/needed
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")]
private interface IAudioMeterInformation
{
float GetPeakValue();
// the rest is not defined/needed
}
}
}
'#
While(1) {
$ReSoinIsActive = Get-Process ReSoin -ErrorAction SilentlyContinue
if($ReSoinIsActive -ne $null) {
$ReSoinIsPlayingSounds = [Foo.Bar]::IsWindowsPlayingSound()
if($ReSoinIsPlayingSounds -eq $False) {
Stop-Process -Name "ReSoin" -Force
Start-Process -FilePath "C:\Program Files (x86)\ReLANpro\Cloud And Class Student\ReSoin.exe"
}else {
# Placeholder used for testing, please ignore
}
}else{
# Placeholder used for testing, please ignore
}
start-sleep -seconds 1
}
This is functional, but it has an obvious flaw. If any other program is playing noise, the computer will assume ReSoin is working. Is there a way to adapt this so that it will only detect if ReSoin is playing noise? I'm an amateur with Powershell and batch scripting, but I'm happy to try other scripting methods if anyone has another idea. If my goal is impossible to achieve, or too difficult to ask of the StackOverflow community, please let me know. Any help would be greatly appreciated.

Setting StrongAuthenticationUserDetails PhoneNumber for AzureAD via Powershell?

That title really flows.
When setting up computers for use with Azure Active Directory, we would have IT do initial setup and config. This included the first sign in and joining to Azure Active Directory. When signing in it forces you to select a verification method. We would use our desk phone or cell phone for ease.
The time has come for us to update that second factor phone number. I know of a way to manually do it via the Azure AD Web UI, but I am looking for a scripted way to set that number in PowerShell.
Here is how I retrieve the number via PowerShell.
Get-msoluser -UserPrincipalName "email#emailaddress.com" | Select-Object -ExpandProperty StrongAuthenticationUserDetails
That code returns this info:
ExtensionData : System.Runtime.Serialization.ExtensionDataObject
AlternativePhoneNumber :
Email :
OldPin :
PhoneNumber : +1 5554445555
Pin :
However, there seems to be no similar option for setting the StrongAuthenticationUserDetails.
All my searches just turned up how to bulk enable 2-factor authentication, which is not what I want to do. I want to leave the StrongAuthentication the same while only updating the phone number.
As I said in comment, it appears there is read-only access for powershell.
There is even opened ticket for that on Azure feedback.
There is a plan to do it, but no ETA. My guess is that you will have to wait if you want to use powershell only.
As workaround, you could use powershell & watir for .NET OR Watin with Watin recorder to automatize it via Internet Explorer. As I don't have a testing Azure; I can not create workable code for you.
Using Watin and powershell - you could check: https://cmille19.wordpress.com/2009/09/01/internet-explorer-automation-with-watin/
The following text and code, I wanted to backup it here, was taken from the above page (all credits to the author):
Next click the record button and click the HTML element you want to
automate. Then stop the WatIN recorder and click copy code to
clipboard icon. This will produce some C# code that just needs to be
translated into PowerShell:
// Windows
WatiN.Core.IE window = new WatiN.Core.IE();
// Frames
Frame frame_sd_scoreboard = window.Frame(Find.ByName("sd") && Find.ByName("scoreboard"));
// Model
Element __imgBtn0_button = frame_sd_scoreboard.Element(Find.ByName("imgBtn0_button"));
// Code
__imgBtn0_button.Click();
window.Dispose();
So, I now know the name of the button and that it is 3 frames deep. A
little WatIN object exploration later, I came up with the follow
script, which clicks a button every 50 mintues.
#Requires -version 2.0
#powershell.exe -STA
[Reflection.Assembly]::LoadFrom( "$ProfileDirLibrariesWatiN.Core.dll" ) | out-null
$ie = new-object WatiN.Core.IE("https://sd.acme.com/CAisd/pdmweb.exe")
$scoreboard = $ie.frames | foreach {$_.frames } | where {$_.name –eq ‘sd’} | foreach {$_.frames } | where {$_.name –eq ‘scoreboard’}
$button = $scoreboard.Element("imgBtn0_button")
while ($true)
{
$button.Click()
#Sleep for 50 minutes
[System.Threading.Thread]::Sleep(3000000)
}
Disclaimer: the code is provided as-is. It might happen that it'll stop working in case MS changes Azure Portal interface.
I'm using the following Greasemonkey script to update alternate email and phone (phone can be updated via Graph API now so the script will be useful for email only):
// ==UserScript==
// #name Unnamed Script 548177
// #version 1
// #grant none
// #namespace https://portal.azure.com
// ==/UserScript==
(function(){
document.addEventListener('keydown', function(e) {
// press alt+shift+g
if (e.keyCode == 71 && e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
const url = document.URL;
const regex = /https:\/\/portal.azure.com\/#blade\/Microsoft_AAD_IAM\/UserDetailsMenuBlade\/UserAuthMethods\/userId\/[\w-]+\/adminUnitObjectId[\/]*\?\w+=(\d{9})&\w+=([\w\.-#]+)/;
const params = url.match(regex);
const allAuthRows = document.getElementsByClassName('ext-userauthenticationmethods-section-row');
const authRowsArray = Array.from(allAuthRows);
let emailRow;
let phoneRow;
let i;
for (i =0; i < authRowsArray.length; i++) {
if (authRowsArray[i].childNodes[1].childNodes[1].childNodes[0].data === 'Email') {
emailRow = authRowsArray[i]
}
if (authRowsArray[i].childNodes[1].childNodes[1].childNodes.length > 1) {
if (authRowsArray[i].childNodes[1].childNodes[1].childNodes[1].childNodes[0].data === 'Phone') {
phoneRow = authRowsArray[i]
}
}
}
const emailInput = emailRow.childNodes[3].childNodes[1].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[2];
const phoneInput = phoneRow.childNodes[3].childNodes[1].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[2];
const event = new Event('input', {
'bubbles': true,
'cancelable': true
});
if (params[1] !== '000000000') {
phoneInput.value = `+48 ${params[1]}`;
phoneInput.dispatchEvent(event);
}
if (params[2] !== 'null') {
emailInput.value = params[2];
emailInput.dispatchEvent(event);
}
setTimeout(() => {
const buttonArr = document.getElementsByClassName('azc-toolbarButton-container fxs-portal-hover');
const saveButton = Array.from(buttonArr).find(e => e.title === 'Save');
saveButton.click();
} , 2000);
}
}, false);
})();
It requires you to open Azure portal with querystring like this (I do it with PowerShell):
https://portal.azure.com/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/UserAuthMethods/userId/$($u.ObjectId)/adminUnitObjectId/?param1=$newPhone&param2=$newMail
How to use it:
open only one tab at a time, otherwise you'll receive Unable to sign-in error
from time to time you'll receive that error anyway, so just wait
to trigger the script press Alt+Shift+g after the site is loaded (you can change the shortcut in first if)
once the data is updated and saved, press Ctrl+w to close the current tab and press Alt+Tab to switch to previous window (should be PowerShell)
you're still free to use the code to update the phone. Make sure to change country code (currently +48 for Poland)

How to find the minimum screen BufferWidth in PowerShell?

I can use $host.UI.RawUI.MaxPhysicalWindowSize.Width to get the maximum width (i.e. number of columns) for the PowerShell command window, and setting the size of the PowerShell command window is well documented, but the minimum buffer width seems to vary between machines. On one machine it was 13 but on another it was 14. I can set the minimum window height and width to 1 and the minimum buffer height can also be 1.
Does anyone know how I can obtain this minimum buffer width value programatically (without just trying values and catching the exceptions!)
Because setting $host.UI.RawUI.BufferSize affects the buffer of its console screen, (Command Prompt -> Properties -> Layout -> Screen Buffer Size is modified when you change $host.UI.RawUI.BufferSize), it has the same restrictions on its buffersize as the console screen.
As we can read here, the specified dimensions for the buffersize cannot be less than the minimum size allowed by the system. This minimum depends on the current font size for the console (selected by the user) and the SM_CXMIN and SM_CYMIN values returned by the GetSystemMetrics function.
One implication of this, is that the larger your console screen font is, the smaller you can make the buffersize.
As an example: here's how to get the minimum width of a console screen. I'm P/Invoking the GetSystemMetrics function from User32.dll using this advanced function (New-PInvoke by Joel Bennett).
$SM_CXMIN =28 # "The minimum width of a window, in pixels." enum value
New-PInvoke -Library User32.dll -Signature "int GetSystemMetrics(uint Metric)"
GetSystemMetrics $SM_CXMIN # returns 132 on my system
To retrieve the size of the font used by the console screen buffer, try GetConsoleFontSize from kernel32.dll.
Note:
The value returned by GetSystemMetrics $SM_CXMIN is the total width (including the borders) of the console screen.
I came across this post in my Google searches. The link for "New-PInvoke by Joel Bennett" provided by "jon Z" is long gone. Using the internet archive I found a single capture from back in 2015. For the sake of preservation here is the function.
function New-PInvoke
{
<#
.Synopsis
Generate a powershell function alias to a Win32|C api function
.Description
Creates C# code to access a C function, and exposes it via a powershell function
.Example
New-PInvoke -Library User32.dll -Signature "int GetSystemMetrics(uint Metric)"
.Parameter Library
A C Library containing code to invoke
.Parameter Signature
The C# signature of the external method
.Parameter OutputText
If Set, retuns the source code for the pinvoke method.
If not, compiles the type.
#>
param(
[Parameter(Mandatory=$true,
HelpMessage="The C Library Containing the Function, i.e. User32")]
[String]
$Library,
[Parameter(Mandatory=$true,
HelpMessage="The Signature of the Method, i.e.: int GetSystemMetrics(uint Metric)")]
[String]
$Signature,
[Switch]
$OutputText
)
process {
if ($Library -notlike "*.dll*") {
$Library+=".dll"
}
if ($signature -notlike "*;") {
$Signature+=";"
}
if ($signature -notlike "public static extern*") {
$signature = "public static extern $signature"
}
$name = $($signature -replace "^.*?\s(\w+)\(.*$",'$1')
$MemberDefinition = "[DllImport(`"$Library`")]`n$Signature"
if (-not $OutputText) {
$type = Add-Type -PassThru -Name "PInvoke$(Get-Random)" -MemberDefinition $MemberDefinition
iex "New-Item Function:Global:$name -Value { [$($type.FullName)]::$name.Invoke( `$args ) }"
} else {
$MemberDefinition
}
}
}
Maybe I'm in wrong but with
[system.console]::BufferWidth
you get the actual buffer width size.
This value can't be less than the current [System.Console]::WindowWidth size (will throw an exception).

How to hide console window of subprocess?

I'm trying to write a very simple program to replace an existing executable. It should munge its arguments slightly and exec the original program with the new arguments. It's supposed to be invoked automatically and silently by a third-party library.
It runs fine, but it pops up a console window to show the output of the invoked program. I need that console window to not be there. I do not care about the program's output.
My original attempt was set up as a console application, so I thought I could fix this by writing a new Windows GUI app that did the same thing. But it still pops up the console. I assume that the original command is marked as a console application, and so Windows automatically gives it a console window to run in. I also tried replacing my original call to _exec() with a call to system(), just in case. No help.
Does anyone know how I can make this console window go away?
Here's my code:
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
char* lpCmdLine,
int nCmdShow)
{
char *argString, *executable;
// argString and executable are retrieved here
std::vector< std::string > newArgs;
// newArgs gets set up with the intended arguments here
char const ** newArgsP = new char const*[newArgs.size() + 1];
for (unsigned int i = 0; i < newArgs.size(); ++i)
{
newArgsP[i] = newArgs[i].c_str();
}
newArgsP[newArgs.size()] = NULL;
int rv = _execv(executable, newArgsP);
if (rv)
{
return -1;
}
}
Use the CreateProcess function instead of execve. For the dwCreationFlags paramter pass the CREATE_NO_WINDOW flag. You will also need to pass the command line as a string as well.
e.g.
STARTUPINFO startInfo = {0};
PROCESS_INFORMATION procInfo;
TCHAR cmdline[] = _T("\"path\\to\\app.exe\" \"arg1\" \"arg2\"");
startInfo.cb = sizeof(startInfo);
if(CreateProcess(_T("path\\to\\app.exe"), cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startInfo, &procInfo))
{
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
}
Aha, I think I found the answer on MSDN, at least if I'm prepared to use .NET. (I don't think I'm really supposed to, but I'll ignore that for now.)
System::String^ command = gcnew System::String(executable);
System::Diagnostics::Process^ myProcess = gcnew Process;
myProcess->StartInfor->FileName = command;
myProcess->StartInfo->UseShellExecute = false; //1
myProcess->StartInfo->CreateNowindow = true; //2
myProcess->Start();
It's those two lines marked //1 and //2 that are important. Both need to be present.
I really don't understand what's going on here, but it seems to work.
You need to create a non-console application (i.e. a Windows GUI app). If all this app does is some processing of files or whatever, you won't need to have a WinMain, register any windows or have a message loop - just write your code as for a console app. Of course, you won't be able to use printf et al. And when you come to execute it, use the exec() family of functions, not system().