4

I am trying to programmatically determine if a .ps1 script is running visibly or not. If it is running visibly, it should restart itself hidden. If it is already hidden, take no action.

The problem I have is a loop where it continually restarts itself because hidden status cannot be determined.

I've been looking at both get-process cmdlet and GWMI Win32_process and see nothing like a .visible property to check status.

    If ($me -eq visible ???)
{
$Invisible = New-Object System.Diagnostics.ProcessStartInfo
$Invisible.FileName = "PowerShell.exe"
$Invisible.windowStyle ="Hidden"
$Invisible.arguments = "$myInvocation.MyCommand.Definition"
$Invisible.Verb = 'runas'
[System.Diagnostics.Process]::Start($Invisible)
}

Any idea what field I can If -eq against ???

Knuckle-Dragger
  • 6,644
  • 4
  • 26
  • 41
  • or maybe my question is how do I Get - Set the WindowStyle of a running process from powershell so that I do not have to re-invoke. – Knuckle-Dragger Dec 21 '13 at 10:56
  • 1
    I would use two scripts. One would be a launcher, and the other would be the main script. That would make things a lot simpler, especially for debugging. It's better to be straight-forward than clever in my opinion. You would still however need to be able to detect if the main script is running. I would use a Windows Semaphore object. But that's a different question, so I won't answer it here. If you asked that question ("How to determine if a PowerShell script it running"), I could answer it. – dan-gph Dec 21 '13 at 11:09
  • am specifically trying to avoid the second file. – Knuckle-Dragger Dec 22 '13 at 05:39

4 Answers4

5

Try using the user32 function 'IsWindowVisible'

If (-not ([System.Management.Automation.PSTypeName]'My_User32').Type) {
Add-Type -Language CSharp -TypeDefinition @"
    using System.Runtime.InteropServices;
    public class My_User32
    { 
        [DllImport("user32.dll")]
        public static extern bool IsWindowVisible(int hwnd);
    }
"@
}

$proc = Start-Process powershell.exe -WindowStyle Hidden -ArgumentList $myInvocation.MyCommand.Definition -Verb runas -PassThru
If ([My_User32]::IsWindowVisible($proc.MainWindowHandle)) {
    #Window is visible
}
Else {
    #Window is not visible
}

Note that the return value for 'isWindowVisible' isn't strictly a boolean. It returns the WS_VISIBLE style bit of a window. Because the value for hidden is zero and the value for visible is nonzero, it will work as a boolean. But if you want to be safe, you can rewrite the If statement to check for -ne 0 to determine if visible.

Also note the use of $proc.MainWindowHandle. You can't use $proc.Handle, as that is not the handle of the parent window.

For more information on the 'IsWindowVisible' function, see Microsoft documentation at:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms633530%28v=vs.85%29.aspx

For more information on window styles, see Microsoft documentation at:
http://msdn.microsoft.com/en-us/library/czada357.aspx

MikeAtWake
  • 51
  • 1
  • 4
1

You can get the StartInfo properties by capturing new the process:

$proc = [System.Diagnostics.Process]::Start($Invisible)
$proc.StartInfo.WindowStyle

You could also start the process and set its StartInfo using the Start-Process cmdlet

$proc = Start-Process powershell.exe -WindowStyle Hidden -ArgumentList $myInvocation.MyCommand.Definition -Verb runas -PassThru
$proc.StartInfo.WindowStyle
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • I couldn't get this one to work either, it does spawn invisibly, but WindowStyle still returns "Normal" and Start-process didn't send WMI the full commandline which foiled the workaround, had to go VBS for re-spawn. – Knuckle-Dragger Dec 22 '13 at 05:41
  • Both examples failed? – Shay Levy Dec 22 '13 at 07:13
  • yeah, even though the command says Hidden, the data field 'WindowStyle' spits out Normal when queried. – Knuckle-Dragger Dec 22 '13 at 07:35
  • Tested that on 2.0 but have users on 2.0 - 4.0 (7/8/8.1). I will check in 2012-R2 to see if 4.0 reports $profit. Tricky issue might be the loop, when it respawns and exits, the contents of the $proc variable will dispose() and when the second instance restarts I cannot call $proc directly from the first IF statement in the first line. – Knuckle-Dragger Dec 22 '13 at 16:40
  • tested on 2012-R2 / 4.0, it does return Hidden while the script is still alive, but once the script ends the $proc variable is disposed and reading the data from Get-Process by $pid yields Normal (even though window is hidden). Tested with out-file -append to generate viewable results between visible/invisible instances. – Knuckle-Dragger Dec 24 '13 at 02:55
  • 1
    I couldn't get any formal answer about this, maybe because of the holidays. I suggest you open a bug report at connect.microsoft.com/powershell. – Shay Levy Dec 24 '13 at 07:59
1

From within the process you can deterine if it's running hidden by testing:

(get-process -Id $PID).StartInfo.WindowStyle
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • 1
    checking this field, hidden windows powershell.exe instances report 'Normal', i confirmed this by running the `(get-process -Id 2716).StartInfo` against the hidden PID. – Knuckle-Dragger Dec 21 '13 at 21:29
  • Now the question is, should I delete this answer, or leave it's carcass hanging on the fence as a warning? – mjolinor Dec 21 '13 at 21:56
  • 1
    I'd leave it to dissuade the others, at least until we track down the real location of the setting. – Knuckle-Dragger Dec 21 '13 at 22:39
1

I've created a kludge but it is far from an answer. It has some limitations in that it will false report if the file or folder path contains "Hidden" in it. It also requires calling itself from vbs method because internal Start-Process cmdlet does not report the correct wmi_win32process.commandline like vbs.shell does.

If ((gwmi win32_process -filter "ProcessID=$PID" | select commandline).commandline -notmatch 'Hidden')
{
$INVISIBLE = $myInvocation.MyCommand.Definition
$COMMAND = "powershell.exe -nologo -WindowStyle Hidden -command $INVISIBLE"
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic');[Microsoft.VisualBasic.Interaction]::Shell("$COMMAND",0)
exit
}

So what it is reading in the commandline is this. On right click, run with powershell (aka run visibly) it results in a wmi.commandline of

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-file" "B:\INVISIBLE.ps1"

But when re-spawned via the VBS command we can grep for 'Hidden' because VBS plays well with WMI and sends the entire command into the value.

powershell.exe -nologo -WindowStyle Hidden -command B:\INVISIBLE.ps1

Still interested in an answer with a wmi property we can read or method we can toggle.

Community
  • 1
  • 1
Knuckle-Dragger
  • 6,644
  • 4
  • 26
  • 41