Hide empty console window in a all GUI Powershell script? - winforms

I have made a very simple Powershell script with WinForms GUI.
Everything works as intended but, when I run the .ps1 script with PowerShell a black empty console window appears at first and then the GUI shows.
Anyway to make the console window dissapear?
Best regards

I wrote a small article on this subject (sorry in french) one year ago.
Here is the common solution using a small VBS script to start PowerShell hidding his window (the trick is in the last ,0).
Set Args = Wscript.Arguments
'MsgBox "Chemin LDAP: " & Args(0)
'MsgBox "Classe: " & Args(1)
Set objShell = CreateObject("Wscript.Shell")
objShell.Run "c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -Noninteractive -file c:\SlxRH\RhModif.ps1 " & chr(34) & Args(0) & chr(34) , 0
I also embeded PowerShell in an executable with no console called slxPShell2.EXE.

I found the above didn't work for me. I used this:
Set objShell = CreateObject("WScript.Shell")
objShell.Run "CMD /C START /B " & objShell.ExpandEnvironmentStrings("%SystemRoot%") & "\System32\WindowsPowerShell\v1.0\powershell.exe -file " & "YourScript.ps1", 0, False
Set objShell = Nothing
Hope that helps.

This solution Minimizes Powershell window after it starts. Powershell window opens, then disapears, without using any outside code. Put at beginning of your script.
$t = '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);'
add-type -name win -member $t -namespace native
[native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle, 0)

This is how I got this working:
Have the Winforms GUI script in one ScriptOne.ps1 file
Create another LaunchScriptOne.ps1 file with the content:
powershell.exe -WindowStyle Hidden -File "C:\path\to\ScriptOne.ps1".
The solution was provided in another thread on the same topic: Hide or Minimize the powershell prompt after Winform Launch
I hope someone will find a way to put this into one single script as well. The answers above in this thread did not help me, but maybe I did something wrong, idk.

I'm nube so no rep so can't comment inline... though wrt #Ipse's solution which I'm a fan of, I also make sure to close the hidden window when the script is done... not sure if PS gets around to this sort of auto-garbage collection, but suspect it's good best practice.
eg. at end of your script I'd suggest doing:
stop-process -Id $PID
(which should terminate that hidden window v. just leave it lurking around and tying up those resources).

Related

Detect Keystrokes In Different Window with Batch

Fair people of stack overflow! My question today is how to detect key presses, but not in the batch window itself. I have this code already:
choice /c KEY /n /t 5 /d d
if %errorlevel%==1 goto restofcode
This only works if you select the batch window itself. What I need is a way, in batch (or some other language) to make it detect if you press Ctrl+W outside of the actual batch command line itself.
There is a program called GS Auto-Clicker and it starts clicking your mouse when you press a key. I want something like this as far as detecting what key you press.
Okie doke, here's an example demonstrating GetAsyncKeyState() using a Batch + PowerShell hybrid script. Save it with a .bat extension. Hopefully the inline comments sufficiently explain how it works. See the MSDN page on GetAsyncKeyState() for full details on that function's expected arguments and return value type.
Edit: Fixed bugs. Original answer would trigger if the user pressed and released W then pressed Ctrl. It seems my PowerShell 2.0 interpreter has some buggy behavior. Setting variables to the GetAsyncKeyState() return value on each loop iteration seems to fix it. I also made the key definitions a little less neck-beardy by leveraging the System.Windows.Forms.Keys collection, and removed the possibility that the "recently pressed" bit would trigger a false positive for "currently pressed".
<# : batch portion (begins PowerShell multiline comment block)
#echo off & setlocal
set /P "=Waiting for ctrl-W... "<NUL
rem # re-launch self with PowerShell interpreter
powershell -noprofile "iex (${%~f0} | out-string)"
echo Pressed. Toodles.
goto :EOF
: end batch / begin PowerShell chimera #>
# import GetAsyncKeyState()
Add-Type user32_dll #'
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(int vKey);
'# -namespace System
# for Keys object constants
Add-Type -As System.Windows.Forms
function keyPressed($key) {
return [user32_dll]::GetAsyncKeyState([Windows.Forms.Keys]::$key) -band 32768
}
while ($true) {
$ctrl = keyPressed "ControlKey"
$W = keyPressed "W"
if ($ctrl -and $W) { break }
start-sleep -milliseconds 40
}
$Host.UI.RawUI.FlushInputBuffer()

Batch icon message on task bar

Is it possible to make a Batch, or .vbs if needed, is it possible to make batch have a little icon down there with the flag, battery and volume.
I want it to be a "Shutting down in xx minutes/hours", and mayyybbe clicking it cancels' it.
Thanks in advance :)
Well, I know how to accomplish part of what you want -- making a systray balloon tip by borrowing from PowerShell. But I don't know how to make it listen for dismissal of the balloon. Maybe someone else can offer another answer building upon mine?
Anyway, I use this for a conversion script I made to convert flac to mp3 in batches. Feel free to hack it for your own evil purposes.
#echo off
setlocal
for %%I in (*.flac) do (
rem // This initiates the systray balloon.
call :systray converting from "%%~nxI" to "%%~nI.mp3"
)
goto :EOF
rem // Here's the :systray function
:systray <message>
setlocal enabledelayedexpansion
set "args=%*"
set "args=!args:'=''!"
set "code="[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');^
$o=New-Object Windows.Forms.NotifyIcon;$o.Icon='%systemroot%\System32\PerfCenterCpl.ico';^
$o.BalloonTipIcon='Info';$o.BalloonTipText='!args!';$o.BalloonTipTitle='%~nx0';$o.Visible=1;^
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit""
start /b "" "powershell" %code%
endlocal & goto :EOF
The n values in $o.ShowBalloonTip(n1) and Start-Sleep -Mn2 are in milliseconds. Salt to taste.
Update: I found a bit about registering an event for $o.BalloonTipClicked as well as a lovely example in the wild. Basically, replace this:
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit
... with this:
$o.ShowBalloonTip(10000);register-objectevent $o BalloonTipClicked clicked;^
if (wait-event clicked -t 12) {$true} else {$false}; $o.Dispose(); Exit
You also need to execute powershell in a single threaded apartment for the event to work.
start /b "" "powershell" -STA %code%
Now, you need to figure out how to make that relevant back in the context of your batch process. For one thing, you'd probably no longer be able to use start /b to make the balloon tip non-blocking, and you'd probably use a for /F loop to capture the output of the powershell command.
Adding to your worries, I propose that "Shutting down in xx minutes" is not entirely user-friendly. What if "Shutting down in 30 minutes" appeared 29 minutes ago, but the user just now saw it? "Shutting down at 9:51 AM" might be better.
So with all this in mind, since what you want is event driven and since the batch language doesn't handle date-time math all that easily, I suggest doing the whole damn thing in PowerShell. Save this with a .ps1 extension. Right-click and run with PowerShell. Or if you want to execute it from a cmd console, do powershell ".\scriptname.ps1".
set-executionpolicy remotesigned
if ([threading.thread]::CurrentThread.GetApartmentState() -eq "MTA") {
& powershell.exe -window minimized -sta $MyInvocation.MyCommand.Definition
exit
}
[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$minutes = 30
$launch_time = (Get-Date).AddMinutes($minutes).ToShortTimeString()
$o = New-Object Windows.Forms.NotifyIcon
$o.Icon = "$env:SystemRoot\System32\PerfCenterCpl.ico"
$o.BalloonTipIcon = "Info"
$o.BalloonTipText = "Shutting down at $launch_time"
$o.BalloonTipTitle = "Shutdown pending..."
$o.Visible = 1
function show-balloon { $o.ShowBalloonTip($minutes * 60 * 1000) }
show-balloon
$o_hover = [Windows.Forms.MouseEventHandler]{ show-balloon }
$o.add_MouseMove($o_hover)
register-objectevent $o BalloonTipClicked clicked
if (wait-event clicked -t ($minutes * 60)) {
remove-event clicked
$o.BalloonTipText = "Have a nice day!"
$o.BalloonTipTitle = "Shutdown aborted"
$o.ShowBalloonTip(10000)
if (wait-event clicked -t 10) { remove-event clicked }
} else {
# Initiate shutdown sequence on my mark. Authorization rojo alpha 3. Mark.
stop-computer
}
unregister-event clicked
$o.Dispose()
Bill_Stewart, if you're reading this, I know you're pleased. As it happens, PowerShell is indeed the correct tool for the job this time.

Running Powershell command in a command line/batch file

I am creating a batch file which involves converting a SID to local/domain username. Since we can not achieve this using the command prompt I am planning to use powershell and I have the PS command as well. I am able to run it in powershell console without any issue, but not sure how to use it in command prompt as a SINGLE LINE(to use it in batch file). I have already tried the below.
Powershell command which works perfectly in PS console -
([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value
Command lines which I have already tried but with no success -
powershell -command ([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value
powershell -command {([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value}
What am I doing wrong? Is it due to any escape characters or am I missing any powershell command parameters? Any help is greatly appreciated.
powershell -command "([System.Security.Principal.SecurityIdentifier]('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value"
worked for me. Just a question of changing the innermost double-quotes to singles around the SID.
Alternatively, escape them with a backslash
powershell -command "([System.Security.Principal.SecurityIdentifier](\"S-1-5-32-544\")).Translate([System.Security.Principal.NTAccount]).Value"
Here is a short VBScript script that uses WMI to do the conversion for you:
Dim SID
SID = WScript.Arguments.Item(0)
Dim SWbemServices, SWbemObject
Set SWbemServices = GetObject("winmgmts:root/CIMV2")
Set SWbemObject = SWbemServices.Get("Win32_SID.SID='" & SID & "'")
WScript.Echo SWbemObject.ReferencedDomainName & "\" & SWbemObject.AccountName
You would of course need to capture this script's output from your shell script (batch file).
You're actually really close. What you want to do is this:
powershell -command "& {([System.Security.Principal.SecurityIdentifier]('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value}"
I tested this using the Get-WmiObject cmdlet. In a standard, but elevated cmd shell I entered:
powershell -command "& {get-wmiobject win32_operatingsystem}"
At that point the wmi object data as returned by powershell was written to the console. You can also load the command into a variable like so:
set wmi=powershell -command "& {get-wmiobject win32_operatingsystem}"
If you call the variable %wmi% there's a delay before it prints. The command itself is in the variable so everytime you call the variable it'll execute the powershell code and return the result.
An alternative answer is to use a base64 encodedcommand switch.
#ECHO OFF
powershell -encodedcommand "KABbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBTAGUAYwB1AHIAaQB0AHkASQBkAGUAbgB0AGkAZgBpAGUAcgBdACgAIgBTAC0AMQAtADUALQAzADIALQA1ADQANAAiACkAKQAuAFQAcgBhAG4AcwBsAGEAdABlACgAWwBTAHkAcwB0AGUAbQAuAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4ATgBUAEEAYwBjAG8AdQBuAHQAXQApAC4AVgBhAGwAdQBlAA=="
PAUSE
When decoded, you'll see it's the OP's original snippet (with the double quotes preserved). Maybe overkill for the OP, but useful for dev's with larger scripts. Plus my original answer was identical to someone elses, so I had to edit.
powershell.exe -EncodedCommand
Accepts a base-64-encoded string version of a command. Use this parameter
to submit commands to Windows PowerShell that require complex quotation
marks or curly braces.

Programmatically send key strokes to a window program in Groovy or bat script

Backstory: I need to programmatically find the differences between two files. I want to use WinMerge to generate a report (Tools -> Generate Report) that I can parse to get the differences between two files. I need this done using either a Groovy script or a bat script.
I was hoping that WinMerge would offer command line options to generate the report and then I could just use a Groovy Process object to execute WinMergeU.exe with the arguments. No such luck according to the command options I've found for WinMerge.
Next, I was hoping to be able to start WinMerge and send keystrokes to step through the menus to generate the report(Alt+T, R, Diff.html, [Enter]). I don't see a way to do that from a Groovy Process and I haven't found a way to do this in a bat script. I'm looking for something similar to WshShell.Sendkeys in VB. Is this a wild-goose chase?
UPDATE/Answer with PowerShell in a bat file:
I was intrigued by Knuckle-Dragger's comment about using a PowerShell script in a bat file.
$folder = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $folder
Start-Sleep -m 1000
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
[Microsoft.VisualBasic.Interaction]::AppActivate("WinMerge")
Start-Sleep -m 100
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
Start-Sleep -m 100
[System.Windows.Forms.SendKeys]::SendWait("%F")
[System.Windows.Forms.SendKeys]::SendWait("o")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
[System.Windows.Forms.SendKeys]::SendWait("%T")
[System.Windows.Forms.SendKeys]::SendWait("r")
Start-Sleep -m 1000
[Microsoft.VisualBasic.Interaction]::AppActivate("Save As")
Start-Sleep -m 1000
[System.Windows.Forms.SendKeys]::SendWait("Diff.txt")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
To encapsulate this in a command window, save it to a file PowerShellScript.ps1:
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta *PowerShellScript.ps1*
Here is a powershell example to activate winmerge and send some keys.
EDIT: Reduced copy pasta with some .NET variables. $SK = Sendkeys $AA = AppActivate $LRA = Reflect .NET.
$startapp = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $startapp
$SK = "[System.Windows.Forms.SendKeys]::SendWait"
$AA = "[Microsoft.VisualBasic.Interaction]::AppActivate"
$LRA = "[void][System.Reflection.Assembly]::LoadWithPartialName"
Start-Sleep 1
$LRA+'("Microsoft.VisualBasic")'
$AA+'("WinMerge")'
Start-Sleep -m 100
$LRA+'("System.Windows.Forms")'
Start-Sleep -m 100
$SK+'("%F")'
$SK+'("o")'
$SK+'("{ENTER}")'
$SK+'("%T")'
$SK+'("r")'
Start-Sleep 1
$AA+'("Save As")'
Start-Sleep 1
$SK+'("Diff.txt")'
$SK+'("{ENTER}")'
To encapsulate this in a command window, save it to a file PowerShellScript.ps1: Note, changed the command syntax a bit, should work if you use the & {.\dot\source\path}
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta -Command "& {.\PowerShellScript.ps1}"
This is something that I threw together without testing, and I don't have WinMerge so testing isn't really an option.
RunMe.bat
start "" "C:\Path\WinMerge.exe" "file1" "file2"
GenerateDiffFile.vbs
GenerateDiffFile.vbs
Set s = CreateObject("WScript.Shell")
wscript.sleep(1000) ' Sleep for 1 second to allow time for WinMerge to finish loading and merge the files
s.SendKeys("%tr")
wscript.sleep(250) ' Give time for dialog box to appear
s.SendKeys("Diff.html{Enter}")
This is completely untested, but I think it is very close to what you need... You can see that the batch-file runs WinMerge, passing the two files to merge on the command line. Then it launches the VBS script, which pauses long enough to allow the launched WinMerge to be able to accept keyboard input. I used this page from Microsoft to know what keys to send.
I've been sending keys to programs for years. It works very reliably, as long as the program you want to send keys to keeps focus.
All I do is this:
echo y | "Batchfile needing the keystroke"

.VBS to run DNSCMD /enumrecords in a for loop on an array of node names. CLI shows as if I ran a DNSCMD /?

I'm trying to run DNSCMD.exe /enumrecords from a .vbs on our Windows Server 2003 DNS server and dump the results into a .csv file for each node name under a DNS Zone.
I have entered the Nodes I want to run the command against under the zone into an array and entered a list of filenames into another array.
The idea being to run a for loop to walk through each node name and file name 0-42 and run the command for each node name and outputting to each filename in sequence.
Option Explicit
Dim ncpArr, NODE, fnameArr, FILE, DNSCMD, objWSHShell, QComm
Set objWSHShell = WScript.CreateObject("WScript.Shell")
ncpArr = Array(42 item array of DNS Nodes)
fnameArr = Array(42 item array of filenames)
QComm = "DnsCmd DNSservername /enumrecords contoso.com " & ncpArr(NODE) & " /Additional> c:\DNSData\" & fnameArr(NODE) & ".csv"
For NODE = 0 to 42
objWSHShell.Run QComm,1,True
'objWshShell.Exec QComm
'wscript.echo "| " & ncpArr(NODE) & " | | " & fnameArr(NODE) & " |"
'wscript.echo DNSCMD
Next
The intent was to save myself some time but if anything I've tripled the time it would have taken to run these 42 commands while writing this script.
What I have figured out so far is:
When I run this command on the DNS server it outputs a CSV exactly like I want it to
DnsCmd dnsservername /enumrecords zone.name node.st.name /Additional /continue> c:\DNSData\state_city_net.csv
When I run the same command like this:
Set objWSHShell = WScript.CreateObject("WScript.Shell")
objWSHShell.run "dnscmd /enumrecords zone.name node.st.name /additional> c:\DNSData\state_city_net.csv"
The command line box pops up briefly showing the instructions for DNSCMD as if I ran a DNSCMD /?. I've only been able to get a look at it by running the loop above and hitting Pause/Break at just the right time. Not sure if it's possible to get the Command Line box to stay open to show any errors
One thing I noticed is normally when you run it with an incorrect syntax it will have some information about the error at the top. In this case it literally looks like the DNSCMD /? instructions with no error at the top.
I'm seriously wondering if it's just not possible to run DNSCMD /enumrecords using a .vbs. as you can see from the commented out parts I've tried a few things to show the syntax is correct. When I Echo the output of the loop I can enter the exact syntax into the command line on the DNS server and it works!
If ANYONE could just peek at what I have above and tell me why when I run my script it does that or if you could even just confirm that my scripting is sound and it's something with DNSCMD it would really help me sleep better.
Thanks
Drew
When you enter
DnsCmd dnsservername /enumrecords zone.name node.st.name /Additional /continue> c:\DNSData\state_city_net.csv
in a shell ('DOS box'), the redirection (>) is provided by this shell. Your
objWSHShell.run "dnscmd /enumrecords zone.name node.st.name /additional> c:\DNSData\state_city_net.csv"
just runs a process and can't do redirection. Change your line to
objWSHShell.run "%comspec% /c dnscmd /enumrecords zone.name node.st.name /additional> c:\DNSData\state_city_net.csv"
and think about the (missing) /continue. For (single) testing, you can change the /c (close when done) to /k (keep open). Probably re-reading about (using?) all three parameters of the .Run method and paying attention to the return value may be a good idea.

Resources