Powershell - Changing X-button behavior of console - powershell

I am working with a script that uses Excel as a COM application, so whenever I close my scipt using the X-button, it will leave an Excel task open in the background.
So I have a function that will close the Exceltask whenever I give "exit" as an user input, but I want to be able to close the console and excel with the X-button in the console. Is there any way that I can change the behavior from the X-button so it will first trigger a function, maybe? To give an idea about the function:
function Close(){
if($workbook){$workbook.close($false)}
if($excel){[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$excel)}
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable excel -ErrorAction SilentlyContinue
exit
}
EDIT
It seems that exiting via the x-button will remain an issue for me as this thread (github) suggests.
So the reason for whom it interests is that:
"The event never kicks in if PowerShell is not in control of its own termination: Thus, closing the window / quitting the terminal emulator will not run the event handler."
which is just what so happens by pressing the x-button. So this issue is being considered in PS 7.0. Furthermore this thread, which discusses the same problem, also helped me find the thread on github.

You can carry out events when the form closes:
$form.Add_FormClosing({
# Actions to carry out when form closed.
})
Edit:
$Null supresses output - you can remove it to see whats going on.
The following code works on my PC:
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -SupportEvent -Action { New-Item -Path c:\temp -Name text.txt }
File created when PS window shut

Related

Question about ISE vs Console with SystemEvents

When I run the following in PowerShell ISE, it works perfectly, gives me the reason "AccountLock" or "AccountUnlock" exactly as it's supposed to. However, when I run this exact command in an elevated powershell console, it does not return the sessionswitch reason at all in console. It returns nothing after an unlock.
I checked Get-EventSubscriber as well as Get-Job and both look successfully created.
Screenshot of Subscriber & Job:
Register-ObjectEvent -InputObject $([microsoft.win32.systemevents]) -EventName "SessionSwitch" -Action {write-host $event.SourceEventArgs.Reason}
One thing I would like to do is have windows detect when the session is unlocked (after a user syncs their password with the domain) and open a program.
OS: Windows 10
Version: 5.1 Build 17134 R 590
After a lot of looking around, i couldn't find a good way to use [windows.win32.systemevents], so i reached out to Lee Holmes. He said it is because the powershell console host runs by default in the Single Thread Apartment (STA) model. If you run it in MTA, it works fine.
Simplest workaround is to use code similar to this at the beginning of your script to ensure you are in MTA mode, and if start a new powershell process if you are not and reload the script.
if ([System.Threading.Thread]::CurrentThread.ApartmentState -ne [System.Threading.ApartmentState]::MTA)
{
powershell.exe -MTA -File $MyInvocation.MyCommand.Path
return
}
Have a look to Microsoft documentation : SystemEvents.SessionSwitch Event, the explanation seems to be that this message is sent to message pump which is the part of code that process graphic messages. You perhaps can try to use an hidden form in your code to force creation of a message pump.
Note :
This event is only raised if the message pump is running. In a Windows service, unless a hidden form is used or the message pump has been started manually, this event will not be raised. For a code example that shows how to handle system events by using a hidden form in a Windows service, see the SystemEvents class.

How to perform logging of forcefully exiting a running script?

I have a powershell script that registers certain events and logs them to a file.
I want to be able to also log to that file the moment that the script was forcefully stopped. For example, by closing the window via X button.
How could I perform this?
We can include in the ecuation the following languages: Powershell via ConEmu console, Perl via ConEmu console, AutoIT.
This does the trick.
Register-EngineEvent PowerShell.Exiting -SupportEvent -Action `
{
#action
}

PowerShell ISE and PowerShell.Exiting event

I'm using the PowerShell.Exiting engine event to save my command history and do some other tidying up when I close a PowerShell session, registering it in my profile thus:
# Set up automatic functionality on engine exit.
Register-EngineEvent PowerShell.Exiting -SupportEvent -Action `
{
#stuff
...
}
This works perfectly when I use PowerShell in a console window, but when I'm running PowerShell in ISE, it appears that the PowerShell.Exiting event somehow never fires, since nothing I put in there, be it the usual stuff or test code, ever runs.
Is this a known problem, and if so, is there a known workaround or alternative?
Well, this is weird as hell.
After cutting down the profile completely outside the Register-EngineEvent call only to find that it still didn't work, I started to cut down the contents of that, too, and restored the rest of the profile to its original state. Here are my findings:
If you have a write-host, or other output to the PowerShell host, in the scriptblock for Register-EngineEvent PowerShell.Exited, it doesn't run when you exit ISE (even though it works fine when you exit the console).
In fact, none of the scriptblocks you have registered against the PowerShell.Exited event appear to run, even ones that don't contain any statements outputting to the host. (Which is why when I tested other people's working examples up above, they didn't work for me unless I started PowerShell without running the profile that added the existing event handler.)
Expunge all statements that cause host output from the scriptblocks you use with Register-EngineEvent PowerShell.Exited , or redirect the output somewhere else, and they all start working.
(Take this with appropriate quantities of salt, since I have not yet had time to go chasing it with a debugger, but I have a sneaking suspicion ISE is closing down its tab before the engine is done with it...)

Disable close button of MSBuild child process

I have a PowerShell script that launches an MSBuild child process. I would like to disable the "close" button on the child process window, so that the user cannot interrupt the process. However, I have not been able to find any information indicating whether this is possible.
If someone could either confirm (and tell me how I would go about doing this) or deny whether this is possible I would greatly appreciate it.
MSBuild.exe is a console application, and as such by default it will run in a console window. You can't really "disable" the close button anymore than you could stop someone (with the right privileges) from just terminating the msbuild.exe process...
What you could do to mitigate some risk would be to use the the jobs feature that was introduced in PowerShell 2.0:
$job = Start-Job -ScriptBlock {
& msbuild app.csproj
if ($LASTEXITCODE -ne 0) { throw "MSBuild failed. Exit code: $LASTEXITCODE" }
}
This will schedule the script block to be run on a background thread of your PowerShell session and it will not show a window for msbuild. All of the output will be captured and held until you decide to retrieve the job. You can check the status of all background jobs with the Get-Job cmdlet, and receive the results with Receive-Job
Wait-Job $job # this line pauses PowerShell/your script until the job returns
$output = $job | Receive-Job
You can do whatever you want with the output - it is worth noting that the exception thrown if the msbuild exit status code is non-zero will be held until you receive the job, at which point it will be raised to your code like any other exception would be. You may want to consider wrapping your call to Receive-Job in a try/catch block to deal with a failed build.
Another option if you don't want a separate window to appear:
$buildArgs = "MySolution.sln", "/t:Build", "/p:Configuration=Debug"
Start-Process -FilePath "msbuild" -ArgumentList $buildArgs -NoNewWindow -Wait
Start-Process has other flags to control redirecting output as well.

Powershell window disappears before I can read the error message

When I call a Powershell script, how can I keep the called script from closing its command window. I'm getting an error and I'm sure I can fix it if I could just read the error.
I have a Powershell script that sends an email with attachment using the .NET classes. If I call the script directly by executing it from the command line or calling it from the Windows Scheduler then it works fine. If I call it from within another script (IronPython, if that matters) then it fails. All scenarios work fine on my development machine. (I really do have to get that "Works on My Machine" logo!) I've got the call to Powershell happening in a way that displays a command window and I can see a flicker of red just before it closes.
Sorry: Powershell 1.0, IronPython 1.1
Solution: powershell -noexit d:\script\foo.ps1
The -noexit switch worked fine. I just added it to the arguments I pass from IronPython. As I suspected, it's something that I can probably fix myself (execution policy, although I did temporarily set as unrestricted with no effect, so I guess I need to look deeper). I'll ask another question if I run into trouble with that.
Thanks to all for the help. I learned that I need to investigate powershell switches a little more closely, and I can see quite a few things that will prove useful in the future.
Try with the -noexit switch:
powershell -noexit d:\script\foo.ps1
You basically have 3 options to prevent the PowerShell Console window from closing, that I describe in more detail on my blog post.
One-time Fix: Run your script from the PowerShell Console, or launch the PowerShell process using the -NoExit switch. e.g. PowerShell -NoExit "C:\SomeFolder\SomeScript.ps1"
Per-script Fix: Add a prompt for input to the end of your script file. e.g. Read-Host -Prompt "Press Enter to exit"
Global Fix: Change your registry key to always leave the PowerShell Console window open after the script finishes running.
Here are the registry keys to modify for option #3:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"& \\\"%1\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"-Command\" \"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & \\\"%1\\\"\""
See my blog for more information and a .reg file that will apply these registry changes automatically.
I've needed this before and usually I didn't want to modify the script (typically for scripts fired off from the Task Scheduler). I just wanted to see what was spit out to console.
All you need to do is just append a Read-Host command after the script invocation e.g.:
PowerShell.exe -command { .\foo.ps1; read-host "Press enter key to continue" }
BTW the problem with using Start-Transcript is that it doesn't capture EXE output. And any form of attempted logging in V1 and even V2 with the standard host will not capture the verbose, debug, progress or warning streams. You can only see these by viewing the associated host window.
One cheesy but effective way to capture all script output (stdout, stderr, verbose, warning, debug) is to use another host like cmd.exe e.g.:
cmd.exe /c powershell.exe "$pwd\foo.ps1" > foo.log
I am generaly fine with scripts autoclosing except when an error occurs, where I need to see the error. Assuming you have not changed $ErrorActionPreference away from the default 'Continue', then for the behaviour I described do this at the end of you script
if ($Error)
{
Pause
}
There is no ordinary Try...Catch construction in Powershell; however you can trap exceptions instead and react properly.
I.E:
Function Example() {
trap [Exception] {
write-host "We have an error!";
write-error $("ERROR: " + $_.Exception.Message);
sleep 30;
break;
}
write-host "Hello world!";
throw "Something very bad has happened!";
}
You can also simulate Try...Catch construction:
Function Example2() {
${
write-host "Our try clause...";
throw "...caused an exception! It hurts!";
}
trap [Exception] {
write-error $_.Exception.Message;
sleep 30;
continue;
}
Of course as soon as you will trap an exception, you can log it, sleep, or whatever you want with the error message. My examples just sleep, allowing you to read what happened, but it's much better to log all the errors. (The simplest way is to redirect them with >>).
Look also at:
http://huddledmasses.org/trap-exception-in-powershell/
A quick and dirty solution is to use CTRL+S to halt the scrolling of the display and CTRL+Q to resume it.
You have three options:
Do a catch in the script (if using
Powershell V2)
Write a dummy
script which catches and redirects
stdout which you can then access as a
variable from your IronPython script.
VBS/Wscript Intro An addition to
this is just liberally drop
Read-Host commands everywhere,
and hit return to page through.
Rather than outputting anything to the shell, wrap your powershell script in a second script that redirects all output to a log file.
PS C:> myscript.ps1 |Out-File myscript.log
Create run_ps_script.bat file containing
#PowerShell.exe -command "try { %1 } finally { read-host 'Press ENTER...' }"
and make it default program to open PowerShell scrips.
My solution was to execute the script with a command line from the console window instead of right-clicking the file -> execute with powershell.
The console keeps displaying the error messages,
even though the execution of the script ended.
Have you thought about redirecting stdout and stderr to a file ex:
./ascript.ps1 >logs 2>&1
Note: You can create wrapper script in powershell that calls your powershell script with all necessary redirections.
My .PS1 script ran fine from the Powershell console but when "double-clicking" or "right-click open with powershell" it would exhibit the 'open/close' problem.
The Fix for me was to rename the script folder to a Name Without Spaces.
Then it all worked - Windows couldn't deal with
"C:\This is my folder\myscript.ps1" but
"C:\This_is_my_folder\myscript.ps1" worked just fine
A couple more ideas...You could use the start-sleep cmdlet at the end of you script to give you enough time to review the error.
You might also be able to use start-transcript to record the session to a text file.