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
Related
I need to track foreground application with application name in window machine. I am using given code but it provide ProcessName not application name example ProcessName is "chrome" Apliication name is "Google Chrome". Either I get application name direclty or i able to mapped Application name with process name.Please help me into this
[CmdletBinding()]
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 = Get-Process | ? {$_.MainWindowHandle -eq $activeHandle}
$Process | Select ProcessName, #{Name="AppTitle";Expression= {($_.MainWindowTitle)}}
}catch{
Write-Error "Failed to get active Window details. More info:$_"
}
Rather than give you the answer (since you may have more questions after) I will teach to to fish for yourself.
Lets say you have a variable and you want to see all of its properties you can get from it; run $variable |get-member
Now you see there are a lot of properties attached to your variable and you don't see any called "application name". So lets list all of the properties of this variable and see what gives us the value we are looking for.
For my example I will grab chrome to put into my variable so we are on the same page.
Here is the code I used to grab the variable to match what you would be working with (ignore this if you already have your variable you're working with).
$variable= Get-Process|? name -ilike chrome|select -first 1
Lets list all the properties
$variable|format-list *
Now we see there are 2 properties that list the name were looking for, Description and Product (for chrome either works, but i don't know which will work for your other use cases, possibly none). Lets grab Product and use that for your code, substituting processname in the select statement (the statement that selects what properties to keep/show in that variable) with the Product Property... Now that you know how, you can change if need be =)
[CmdletBinding()]
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 = Get-Process | ? {$_.MainWindowHandle -eq $activeHandle}
$Process | Select Product, #{Name="AppTitle";Expression= {($_.MainWindowTitle)}}
}catch{
Write-Error "Failed to get active Window details. More info:$_"
}
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'
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')
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
I have a script that may be run manually or may be run by a scheduled task. I need to programmatically determine if I'm running in -noninteractive mode (which is set when run via scheduled task) or normal mode. I've googled around and the best I can find is to add a command line parameter, but I don't have any feasible way of doing that with the scheduled tasks nor can I reasonably expect the users to add the parameter when they run it manually.
Does noninteractive mode set some kind of variable or something I could check for in my script?
Edit:
I actually inadvertently answered my own question but I'm leaving it here for posterity.
I stuck a read-host in the script to ask the user for something and when it ran in noninteractive mode, boom, terminating error. Stuck it in a try/catch block and do stuff based on what mode I'm in.
Not the prettiest code structure, but it works. If anyone else has a better way please add it!
I didn't like any of the other answers as a complete solution. [Environment]::UserInteractive reports whether the user is interactive, not specifically if the process is interactive. The api is useful for detecting if you are running inside a service. Here's my solution to handle both cases:
function Assert-IsNonInteractiveShell {
# Test each Arg for match of abbreviated '-NonInteractive' command.
$NonInteractive = [Environment]::GetCommandLineArgs() | Where-Object{ $_ -like '-NonI*' }
if ([Environment]::UserInteractive -and -not $NonInteractive) {
# We are in an interactive shell.
return $false
}
return $true
}
You can check how powershell was called using Get-WmiObject for WMI objects:
(gwmi win32_process | ? { $_.processname -eq "powershell.exe" }) | select commandline
#commandline
#-----------
#"C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" -noprofile -NonInteractive
UPDATE: 2020-10-08
Starting in PowerShell 3.0, this cmdlet has been superseded by Get-CimInstance
(Get-CimInstance win32_process -Filter "ProcessID=$PID" | ? { $_.processname -eq "pwsh.exe" }) | select commandline
#commandline
#-----------
#"C:\Program Files\PowerShell\6\pwsh.exe"
I think the question needs a more thorough evaluation.
"interactive" means the shell is running as REPL - a continuous read-execute-print loop.
"non-interactive" means the shell is executing a script, command, or script block and terminates after execution.
If PowerShell is run with any of the options -Command, -EncodedCommand, or -File, it is non-interactive. Unfortunately, you can also run a script without options (pwsh script.ps1), so there is no bullet-proof way of detecting whether the shell is interactive.
So are we out of luck then? No, fortunately PowerShell does automatically add options that we can test if PowerShell runs a script block or is run via ssh to execute commands (ssh user#host command).
function IsInteractive {
# not including `-NonInteractive` since it apparently does nothing
# "Does not present an interactive prompt to the user" - no, it does present!
$non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'
# alternatively `$non_interactive [-contains|-eq] $PSItem`
-not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}
Now test in your PowerShell profile whether this is in interactive mode, so the profile is not run when you execute a script, command or script block (you still have to remember to run pwsh -f script.ps1 - not pwsh script.ps1)
if (-not (IsInteractive)) {
exit
}
Testing for interactivity should probably take both the process and the user into account. Looking for the -NonInteractive (minimally -noni) powershell switch to determine process interactivity (very similar to #VertigoRay's script) can be done using a simple filter with a lightweight -like condition:
function Test-Interactive
{
<#
.Synopsis
Determines whether both the user and process are interactive.
#>
[CmdletBinding()] Param()
[Environment]::UserInteractive -and
!([Environment]::GetCommandLineArgs() |? {$_ -ilike '-NonI*'})
}
This avoids the overhead of WMI, process exploration, imperative clutter, double negative naming, and even a full regex.
I wanted to put an updated answer here because it seems that [Environment]::UserInteractive doesn't behave the same between a .NET Core (container running microsoft/nanoserver) and .NET Full (container running microsoft/windowsservercore).
While [Environment]::UserInteractive will return True or False in 'regular' Windows, it will return $null in 'nanoserver'.
If you want a way to check interactive mode regardless of the value, add this check to your script:
($null -eq [Environment]::UserInteractive -or [Environment]::UserInteractive)
EDIT: To answer the comment of why not just check the truthiness, consider the following truth table that assumes such:
left | right | result
=======================
$null | $true | $false
$null | $false | $true (!) <--- not what you intended
This will return a Boolean when the -Noninteractive switch is used to launch the PowerShell prompt.
[Environment]::GetCommandLineArgs().Contains('-NonInteractive')
I came up with a posh port of existing and proven C# code that uses a fair bit of P/Invoke to determine all the corner cases. This code is used in my PowerShell Build Script that coordinates several build tasks around Visual Studio projects.
# Some code can be better expressed in C#...
#
Add-Type #'
using System;
using System.Runtime.InteropServices;
public class Utils
{
[DllImport("kernel32.dll")]
private static extern uint GetFileType(IntPtr hFile);
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
public static bool IsInteractiveAndVisible
{
get
{
return Environment.UserInteractive &&
GetConsoleWindow() != IntPtr.Zero &&
IsWindowVisible(GetConsoleWindow()) &&
GetFileType(GetStdHandle(-10)) == 2 && // STD_INPUT_HANDLE is FILE_TYPE_CHAR
GetFileType(GetStdHandle(-11)) == 2 && // STD_OUTPUT_HANDLE
GetFileType(GetStdHandle(-12)) == 2; // STD_ERROR_HANDLE
}
}
}
'#
# Use the interactivity check somewhere:
if (![Utils]::IsInteractiveAndVisible)
{
return
}
C:\> powershell -NoProfile -NoLogo -NonInteractive -Command "[Environment]::GetCommandLineArgs()"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-NoProfile
-NoLogo
-NonInteractive
-Command
[Environment]::GetCommandLineArgs()
Implement two scripts, one core.ps1 to be manually launched, and one scheduled.ps1 that launches core.ps1 with a parameter.
powerShell -NonInteractive { Get-WmiObject Win32_Process -Filter "Name like '%powershell%'" | select-Object CommandLine }
powershell -Command { Get-WmiObject Win32_Process -Filter "Name like '%powershell%'" | select-Object CommandLine }
In the first case, you'll get the "-NonInteractive" param. In the latter you won't.
Script: IsNonInteractive.ps1
function Test-IsNonInteractive()
{
#ref: http://www.powershellmagazine.com/2013/05/13/pstip-detecting-if-the-console-is-in-interactive-mode/
#powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
return [bool]([Environment]::GetCommandLineArgs() -Contains '-NonInteractive')
}
Test-IsNonInteractive
Example Usage (from command prompt)
pushd c:\My\Powershell\Scripts\Directory
::run in non-interactive mode
powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
::run in interactive mode
powershell -File .\IsNonInteractive.ps1
popd
More Involved Example Powershell Script
#script options
$promptForCredentialsInInteractive = $true
#script starts here
function Test-IsNonInteractive()
{
#ref: http://www.powershellmagazine.com/2013/05/13/pstip-detecting-if-the-console-is-in-interactive-mode/
#powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
return [bool]([Environment]::GetCommandLineArgs() -Contains '-NonInteractive')
}
function Get-CurrentUserCredentials()
{
return [System.Net.CredentialCache]::DefaultCredentials
}
function Get-CurrentUserName()
{
return ("{0}\{1}" -f $env:USERDOMAIN,$env:USERNAME)
}
$cred = $null
$user = Get-CurrentUserName
if (Test-IsNonInteractive)
{
$msg = 'non interactive'
$cred = Get-CurrentUserCredentials
}
else
{
$msg = 'interactive'
if ($promptForCredentialsInInteractive)
{
$cred = (get-credential -UserName $user -Message "Please enter the credentials you wish this script to use when accessing network resources")
$user = $cred.UserName
}
else
{
$cred = Get-CurrentUserCredentials
}
}
$msg = ("Running as user '{0}' in '{1}' mode" -f $user,$msg)
write-output $msg