Opening PowerShell Script and hide Command Prompt, but not the GUI - powershell

I currently open my PowerShell script through a .bat file. The script has a GUI. I tried to put this in my script but it hid the GUI as well and also kept looping because I wanted my GUI to continuously loop:
powershell.exe -WindowStyle Hidden -file c:\script.ps1
How do we run a script without Command Promp, but also not hide the gui? And is that the same if we run the script using a .bat file? Thank you!

If you want to hide the Console behind the GUI I've had success with the following native functions:
# .Net methods for hiding/showing the console in the background
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
function Show-Console
{
$consolePtr = [Console.Window]::GetConsoleWindow()
# Hide = 0,
# ShowNormal = 1,
# ShowMinimized = 2,
# ShowMaximized = 3,
# Maximize = 3,
# ShowNormalNoActivate = 4,
# Show = 5,
# Minimize = 6,
# ShowMinNoActivate = 7,
# ShowNoActivate = 8,
# Restore = 9,
# ShowDefault = 10,
# ForceMinimized = 11
[Console.Window]::ShowWindow($consolePtr, 4)
}
function Hide-Console
{
$consolePtr = [Console.Window]::GetConsoleWindow()
#0 hide
[Console.Window]::ShowWindow($consolePtr, 0)
}
Once the above functions have been added to your Form, simply call the Hide-Console function in your Form_Load event:
$OnFormLoad =
{
Hide-Console
}
If you need to show the Console, perhaps for debugging, you can easily show the console again by calling the Show-Console function:
$OnButtonClick =
{
Show-Console
}
For more information on the numbers you can pass to ShowWindow you can check out the ShowWindow documentation on MSDN here
Update based on comment
Thanks for this code. I tried to use it in my script, but where exactly am I suppose to put Hide-Console? My form load looks like this $objForm.Add_Shown({$objForm.Activate()}) [void] $objForm.ShowDialog()
To hide the console with this code, all you need to do is call Hide-Console. Since you already have code in the Shown event ($objForm.Add_Shown) we can simply add another line to call the command:
Change this:
$objForm.Add_Shown({$objForm.Activate()})
To this:
$objForm.Add_Shown({
$objForm.Activate()
Hide-Console
})
When your form is Shown the console will be hidden (You can still call Show-Console if you want to see it later).

If you run PowerShell from a shortcut with the window set to minimized, it will briefly flash a cmd icon in the taskbar but you barely notice it. Yet, it will start your PowerShell GUI without having a console window behind it.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle Hidden -file "C:\path\whatever.ps1"
If you want to start a second GUI console window from whatever.ps1 without it stopping the processing on whatever.ps1 you need to use start-process. However, start-process with -WindowStyle hidden prevents the GUI from showing up. Removing -WindowStyle shows a command window behind your GUI. However, if you start-process with cmd.exe /k, it does work.
$argumentlist = "/c powershell.exe -file `"c:\path\whatever2.ps1`" -param1 `"paramstring`""
Start-Process cmd.exe -WindowStyle Hidden -ArgumentList $argumentlist
As a bonus, if you start whatever2.ps1 with a param() statement, you can pass named, required arguments. Just be sure it's the very first thing in the ps1 file, before assemblies even.
param (
[Parameter(Mandatory=$true)]
[string]$var1
)
$argumentlist = "/c powershell.exe -file `"C:\path\whatever2.ps1`" -param1 `"param1string`""

In the solution proposed posted in a comment (*
Put it in a shortcut instead of in a batch file. Right click a blank
spot on desktop or in a folder window, New, Shortcut, paste your line
in, Next, name it, Finish
*.) to hide definitely the Command Prompt, I set, in the properties of the shortcut Run=minimized in General.

Simplified the function for show hide a bit
function Show-Console
{
param ([Switch]$Show,[Switch]$Hide)
if (-not ("Console.Window" -as [type])) {
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
}
if ($Show)
{
$consolePtr = [Console.Window]::GetConsoleWindow()
# Hide = 0,
# ShowNormal = 1,
# ShowMinimized = 2,
# ShowMaximized = 3,
# Maximize = 3,
# ShowNormalNoActivate = 4,
# Show = 5,
# Minimize = 6,
# ShowMinNoActivate = 7,
# ShowNoActivate = 8,
# Restore = 9,
# ShowDefault = 10,
# ForceMinimized = 11
$null = [Console.Window]::ShowWindow($consolePtr, 5)
}
if ($Hide)
{
$consolePtr = [Console.Window]::GetConsoleWindow()
#0 hide
$null = [Console.Window]::ShowWindow($consolePtr, 0)
}
}
show-console -show
show-console -hide

Related

Bring a program to foreground or start it

For a handful of programs I use frequently, I am trying to write me some functions or aliases which would check if this program is already running and bring its window to foreground, else start this program.
Usage example with np, a handle for notepad.exe:
PS> np
checks if notepad.exe is running (Get-Process -Name "notepad.exe") if not, it would start it. When Notepad is already running but my maximized console is in the foreground, I'd like to execute the same command again, but this time I want it to bring the already running notepad process to foreground, rather than start a new one.
In order to implement this, I created this class called Program which I would instantiate for every program I want to handle like this. Then I have a HashTable $knownprograms of instances of this class, and in the end I try to define functions for every program, so that I could just type two or three letters to the console to start a program or bring its running process back to foreground.
class Program {
[string]$Name
[string]$Path
[string]$Executable
[string[]]$Arguments
Program(
[string]$n,
[string]$p,
[string]$e,
[string[]]$a
){
$this.Name = $n
$this.Path = $p
$this.Executable = $e
$this.Arguments = $a
}
[string]FullPath(){
return ("{0}\{1}" -f $this.Path, $this.Executable)
}
[void]ShowOrStart(){
try {
# Adapted from https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/bringing-window-in-the-foreground
$Process = Get-Process -Name $this.Name -ErrorAction Stop
Write-Host "Found at least one process called $this.Name"
$sig = '
[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
'
$Mode = 4 # Will restore the window, not maximize it
$type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
$hwnd = $process.MainWindowHandle
$null = $type::ShowWindowAsync($hwnd, $Mode)
$null = $type::SetForegroundWindow($hwnd)
} catch [Microsoft.PowerShell.Commands.ProcessCommandException] {
Write-Host "Did not find any process called $this.Name"
Invoke-Command -ScriptBlock { & $this.FullPath() $this.Arguments }
}
}
}
$knownprograms = #{}
$knownprograms.Add("np", [Program]::new(
"np",
"$Env:SystemRoot\System32",
"notepad.exe",
#())
)
$knownprograms.Add("pt", [Program]::new(
"pt",
"$Env:SystemRoot\System32",
"mspaint.exe",
#())
)
Function np {
[cmdletbinding()]
Param()
$knownprograms.np.ShowOrStart()
}
Function pt {
[cmdletbinding()]
Param()
$knownprograms.pt.ShowOrStart()
}
The idea would be that I would source this script in my profile.ps1 and then just use the pre-factored functions. However, it seems that this code always opens a new instance of the program, rather than using its running process. Maybe I need some sort of delayed evaluation, so that the ShowOrStart() method checks at the time of invocation of np or pt whether the associated process exists. Any ideas how to accomplish this?
The process name for notepad.exe is notepad.
Update
$knownprograms.Add("np", [Program]::new(
"notepad",
"$Env:SystemRoot\System32",
"notepad.exe",
#())
)
And this works as expected.
This would be probably interesting to register $sig once for all and not on every call (which will probably raise an error).

How do I put the focus on a modal dialog box using powershell?

I found a script here (see below) that allows me to select a main window from powershell and then add some keypresses. However, when the script selects the main window and not the dialog box I want to do key presses on to make it go away. Is there some way to select the dialog box instead, or use keypresses to select it?
Function SendKey{
[CMDLetBinding()]
Param(
[String]
[Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True,Position=1)]
$WindowTitle,
[String[]]
[Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True,Position=2)]
$Key
)
Begin{
$ErrorActionPreference = 'SilentlyContinue'
$Dlls = #'
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
'#
$WindowControl = Add-Type -MemberDefinition $Dlls -Name "Win32WindowControl" -namespace Win32Functions -passThru
}
Process{
$WindowHandle = Get-Process | Where-Object { $_.MainWindowTitle -Match $WindowTitle } | Select-Object -ExpandProperty MainWindowHandle
If($WindowHandle){
$WindowControl::SetForegroundWindow($WindowHandle)
Sleep 1
$FocusHandle = $WindowControl::GetForegroundWindow()
If($FocusHandle -eq $WindowHandle){
ForEach($Press in $Key){
[System.Windows.Forms.SendKeys]::SendWait("$Press")
}
}
}
}
}
Your code:
has an incidental problem: $ErrorActionPreference = 'SilentlyContinue' suppresses subsequent errors, so that [System.Windows.Forms.SendKeys]::SendWait("$Press") quietly fails, given that the System.Windows.Forms assembly is never loaded in your code (Add-Type -AssemblyName System.Windows.Forms)
has a fundamental problem: Using SetForegroundWindow() with a process' main window will indeed set the focus on that main window even while a modal dialog is open - as a result, keystrokes may go nowhere.
The simplest solution is to use the [Microsoft.VisualBasic.Interaction] type's static .AppActivate() method instead:
.AppActivate() properly activates whatever window belonging to the target application is frontmost - as would be activated if you alt-tabbed to the application. This may be the main window or an open modal dialog, for instance.
Function SendKey {
[CmdletBinding()]
Param(
[String]
[Parameter(Mandatory = $True, Position = 1)]
$WindowTitle,
[String[]]
[Parameter(Mandatory = $True, Position = 2)]
$Key
)
Begin {
# Load the required assemblies.
Add-Type -AssemblyName System.Windows.Forms, Microsoft.VisualBasic
}
Process {
# Find the process with the main window title of interest.
$procId = (Get-Process | Where-Object { $_.MainWindowTitle -Match $WindowTitle }).Id
If ($procId) { # Target application's process found.
# Activate it by its process ID.
[Microsoft.VisualBasic.Interaction]::AppActivate($procId)
# Send the keystrokes.
ForEach ($Press in $Key) {
[System.Windows.Forms.SendKeys]::SendWait($Press)
}
}
}
}
To test the code:
From a PowerShell window, dot-source the function above (define it in your session).
Open a Notepad instance (run notepad).
Switch to the new instance and make the file-open dialog visible (Ctrl+O).
Switch back to your PowerShell window and run SendKey Notepad '{ESC}o'
Notepad should be activated, the file-open dialog should be dismissed, and o should be typed in the main window (the document).
If there's no dialog open, then the {ESC} should have no effect and o should appear in the main window too.
Caveat: The keystrokes will be sent to whatever control inside the target window / target window's open dialog happens to have the keyboard focus.
Therefore, if you know what specific window / dialog will be open at the time you send the keystrokes, you can first send additional keystrokes that activate the specific control of interest.
For instance, if you expect the file-open dialog to be open, you could send %n - the equivalent of Alt+N - first, to ensure that the File name: text box has the input focus.
E.g., to send filename file.txt: SendKey Notepad '%nfile.txt'

Toggle Scroll Lock on or off

I am using PowerShell in my script to check the status of various keys like NumLock and CapsLock.
powershell.exe -Command [Console]::CapsLock
powershell.exe -Command [Console]::NumberLock
But I found no way to check the status of ScrollLock through PowerShell console command. Can you guys tell me why powershell.exe -Command [Console]::ScrollLock does not work and what is needed to be done?
You can get the ScrollLock key state with the GetKeyState() function from the user32.dll native Windows API:
Add-Type -MemberDefinition #'
[DllImport("user32.dll")]
public static extern short GetKeyState(int nVirtKey);
'# -Name keyboardfuncs -Namespace user32
# 0x91 = 145, the virtual key code for the Scroll Lock key
# see http://www.foreui.com/articles/Key_Code_Table.htm
if([user32.keyboardfuncs]::GetKeyState(0x91) -eq 0){
# Scroll Lock is off
}
else {
# Scroll Lock is on
}
You can also get it by running:
[System.Windows.Forms.Control]::IsKeyLocked('Scroll')

Get foreground windows of windows explorer to retrieve the url

I think am close to the solution, or in the general ball park, but the way am calling the powershell might be part of the issue am calling the ps1 from a batch file, to
I want to have the user click a button and then have powershell get the url of the active window (EXPLORER.exe) - i did try to but the start/sleep command to check in the ISE but could get it to work :( could you help be out ?
Thank You
Set-ExecutionPolicy RemoteSigned
Param(
)
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
try {
$ActiveHandle = [UserWindows]::GetForegroundWindow()
$Process | Select LocationURL| select -ExpandProperty LocationURL -First 1 |
Tee-Object -Variable "dirvar"
} catch {
Write-Error "Failed to get active Window details. More Info: $_"
}
$dirvar1 = "$dirvar" -replace 'file:///', ''
echo "$dirvar1"
Start-Process "Z:\30_Sysadmin\ADB_LOADER_DIR\ADBDIR.bat" "$dirvar1"
You are going about it in a difficult way. There are helper functions. This lists all shell windows (Internet Explorer and Explorer windows). The object is an Internet Explorer object. See https://msdn.microsoft.com/en-us/library/aa752084(v=vs.85).aspx.
This is VBScript but COM calls are identical across all languages.
Set objShell = CreateObject("Shell.Application")
Set AllWindows = objShell.Windows
For Each window in AllWindows
window.Navigate "c:\"
msgbox window.locationURL
Next

How to execute a powershell script without stealing focus?

I made a powershell script that uses GetForegroundWindow() to identify which Window is the currently focused Window. Unfortunately, when the powershell script is executed (via Windows Task Scheduler or a hotkey), the script steals focus of the current foreground app; incorrectly using the Powershell window itself as the Foreground app, instead of the intended foreground app.
I even tried creating an EXE using PS2EXE "-noconsole" setting; however, the script still doesn't doesn't work.
Could someone please suggest a way to execute this script without changing focus of the current foreground Window?
My script code works during tests (ONLY if I add "Start-Sleep -s 5"; and, manually steal back the focus with alt-tab for the script to identify the correct foreground Window.
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class Tricks {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
function Set-WindowStyle {
param(
[Parameter()]
[ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE',
'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED',
'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]
$Style = 'SHOW',
[Parameter()]
$MainWindowHandle = (Get-Process -Id $pid).MainWindowHandle
)
$WindowStates = #{
FORCEMINIMIZE = 11; HIDE = 0
MAXIMIZE = 3; MINIMIZE = 6
RESTORE = 9; SHOW = 5
SHOWDEFAULT = 10; SHOWMAXIMIZED = 3
SHOWMINIMIZED = 2; SHOWMINNOACTIVE = 7
SHOWNA = 8; SHOWNOACTIVATE = 4
SHOWNORMAL = 1
}
Write-Verbose ("Set Window Style {1} on handle {0}" -f $MainWindowHandle, $($WindowStates[$style]))
$Win32ShowWindowAsync = Add-Type -memberDefinition #"
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
"# -name "Win32ShowWindowAsync" -namespace Win32Functions -passThru
$Win32ShowWindowAsync::ShowWindowAsync($MainWindowHandle, $WindowStates[$Style]) | Out-Null
}
$a = [tricks]::GetForegroundWindow()
$title = get-process | ? { $_.mainwindowhandle -eq $a }
$title2 = $title | select -ExpandProperty ProcessName
if ($title2 -eq 'Kodi'){
Set-WindowStyle MINIMIZE $a;
if (Get-Process -Name Yatse2) {(Get-Process -Name Yatse2).MainWindowHandle | foreach { Set-WindowStyle MINIMIZE $_ }}
} ELSE {
$title.CloseMainWindow()
}
I know this question is old, but I've spent quite some time trying to figure out how to not lose focus from the current window for myself and found some information, so hopefully this will help future readers.
The easiest solution is just to literally simulate an Alt+Tab keypress from within your Powershell script instead of having to do it yourself. The following code comes from this StackOverflow answer:
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
[System.Windows.Forms.SendKeys]::SendWait("%{TAB}")
# Retrieve the window handles...
...where % represents the Alt modifier and {TAB} is interpreted as the tab key.
User homersimpson's answer works, but using reflection is a bit slow. You can speed things up by adding the Windows.Forms assembly directly...
Add-Type -AssemblyName System.Windows.Forms
...other imports
#Return focus to the original window.
[System.Windows.Forms.SendKeys]::SendWait("%{TAB}")
...Your Code Here
Again, the % represents Alt and you know what TAB is. You are effectively Alt-Tabbing the new window away, returning focus to your desired one.
Create task that can be run on demand in Task Scheduler and executes your powershell script
Make sure it is set to be run whether user is logged on or not
Create a shortcut that starts the task
That allows your script to be run without visible window