How to Quit current Powershell IE COM session using Try/Catch/Finally - powershell

Looking to terminate IE COM Object browser sessions at the end of my script, in my Finally{} statement.
I would think it be as simple as:
Finally{
$ie.Quit()
}
But its not. the $ie object is unreachable at this stage in the script and no methods can be called on it. Anyone know why this is? Did the $ie object go out of scope?
I have defined the $ie object earlier, before my Try{} statement:
$ie = New-Object -COMObject InternetExplorer.Application
Try{...}
But I dont actually Navigate() until inside the Try{} statement:
$ie = New-Object -COMObject InternetExplorer.Application
Try{
$ie.navigate("http://www.allregs.com/tpl/Main.aspx")
}
Then I have my catch{} statement:
Catch{
write-host “Exception Message: $($_.Exception.Message)” -ForegroundColor Red
}
And lastly my Finally{} statement:
Finally{
$ie.Quit() # 'can't call method on System.ComObject.' Null?
}
Any ideas why I can't Quit() the current $ie process at this stage in the script? Am I missing something? Is there another way to end the current Internet explorer session, without closing ALL of them? Any input welcome. Thanks.
Update:
So it appears $IE is unreachable at the end because the code never reaches the part where $ie actually navigates and gets a value. It only gets initialized at the start. In fact, it appears my Invoke-Webrequest requests (which log me in) are what prompt IE to start up and this is why a call to $ie.Quit() does nothing. My question now, is there a way to close IE sessions started using Invoke-Webrequest? -UseBasicParameter works to supress IE from starting up but it also messes up my code in weird ways so this doesn't seem like an option

You don't really need the finally{} block unless there is something in the catch{} block that would terminate the script/function at that point. If the script/function is able to carry on after the try{}catch{}, then you can just call $ie.Quit():
$ie = New-Object -COMObject InternetExplorer.Application
try {
$ie.navigate("http://www.allregs.com/tpl/Main.aspx")
}
catch {
Write-Host “Exception Message: $($_.Exception.Message)” -ForegroundColor Red
}
$ie.Quit()
Update
In light of the additional information in the question, I don't think that try-catch-finally has anything to do with the actual problem here, so I guess the answer to the title question is that as long as $ie is still a valid application object, the .Quit() method will still work in a finally{} block.

Related

Is there a way to run a popup window while a script runs?

I am creating a gui, and want a popup to let you know it's busy, but then close when it's completed that specific task. The only thing i can find is the following...
$popup = New-Object -ComObject wscript.shell
$popup.popup("Running Script, Please Wait....",0,"Running...",0x1)
But the issue is, this is waiting for a response, and then it will run the script. I am not asking for some to write me a script, but some guidelines on where to find this information.
I need powershell to popup a window, and then leave it up, while a script is ran, and then close it when the script is done running.
Would it be best just just have another windows form, that runs the script with a label on it? That seems like an excessive amount of work for a simple task. But it IS powershell...
Is there something like...
$popup = New-Object -ComObject wscript.shell
$popup.popup("Running Script, Please Wait....",0,"Running...",0x1)
###RUN SCRIPT HERE...
$popup.close()
EDIT:::
To the question "Why am I trying to have a popup, instead of writeprogress or whatnot" ... The reason is because I am doing this in a gui. Not in the command line. So i need the gui to basically inform the person it's busy, some of the tasks can take over 6 hours to complete, and i don't want them clicking around, and doing other things while the current task at hand is running.
EDIT 2:::
I will leave this open, as the original question was not answered, but I created a work around with the following code.
$LabelAlert = New-Object system.windows.forms.label
$LabelAlert.Text = "Working, Please wait."
$LabelAlert.location = New-Object System.Drawing.Point(0,180)
$LabelAlert.width = 590
$LabelAlert.height = 25
$LabelAlert.Visible = $false
$LabelAlert.TextAlign = "TopCenter"
$Form.Controls.Add($LabelAlert)
$FormGroupBox = New-Object System.Windows.Forms.GroupBox
$FormGroupBox.Location = New-Object System.Drawing.Size(0,0)
$FormGroupBox.width = 600
$FormGroupBox.height = 375
$Form.Controls.Add($FormGroupBox)
$startAlert = {
$LabelAlert.Visible = $true
$FormGroupBox.Visible = $false
}
$stopAlert = {
$LabelAlert.Visible = $false
$FormGroupBox.Visible = $true
}
Every form part was moved inside the group box. And the group box is the same size as my window.
And for every time consuming Script i run
&$startAlert
....script commands go here...
&$stopAlert
You could use Start-Job to run the popup within a background job, which would allow the script to continue after it has appeared:
$Job = Start-Job -ScriptBlock {
$popup = New-Object -ComObject wscript.shell
$popup.popup("Running Script, Please Wait....",0,"Running...",0x1)
}
#Run script here..
But I can't see any way to force the popup to close at the end of your script (tried Remove-Job -Force and even Stop-Process conhost -Force but neither seemed to work).
As others have said though, the better option would be write status to the PowerShell window. You might want to look at the Write-Progress cmdlet which you can use to disply a progress bar over a running script.

Get powershell to control Internet Explorer and switch between 2 webpages

I want to have Internet Explorer to switch between 2 different webpages in an endless loop . The webpages is local files, that has to be viewed for 10 seconds before changing to next webpage. My code so far:
$ie = New-Object -Comobject 'InternetExplorer.Application'
$ie.Visible = $true
function IEWeb {
$ie.navigate(file://D:\web\index.html)
Start-sleep 10
$ie.navigate(file://D:\web\index2.html)
Start-sleep 10
}
while($true) {
IEWeb
}
Everything is working until the second webpage has to be loaded.
Then I get an error message:
Object is disconnected from it's clients. (Exception from HRESULT:0x800010108 (RPC_E_DISCONNECTED))
I have tried with global vars but still the same.
Can anyone give Me a hint of what I'm missing?
I wasn't able to produce your issue, but how about this?
function Navigate-Rotate {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[string[]]$Url
,
[Parameter()]
[int]$SleepSeconds = 10
)
Process {
$ie = New-Object -Comobject 'InternetExplorer.Application'
$ie.Visible = $true
while ($ie.Visible) {
foreach ($uri in $Url) {
if ($ie.Visible) {$ie.navigate2($uri)}
Start-Sleep -Seconds $SleepSeconds
if ($ie.Visible) {$ie.Stop()} #just incase anything's running which may interfere
}
}
}
}
#Navigate-Rotate 'https://stackexchange.com','https://google.com'
Navigate-Rotate 'file:///D:/web/index.html', 'file:///D:/web/index2.html'
Notes
I keep checking the value of $ie.Visible. Should a user exit IE this ensures that my code won't try to use any of $ie's methods. NB: This value doesn't get set to false once closed; rather it ceases to exist; but that evaluates as falsey, so has the same effect. There is a potential race condition, but it's minor / can't be avoided (or I don't know how to avoid it).
I use navigate2 instead of navigate since this method's a bit more flexible / there's no drawback.
I use $ie.Stop() after loading each page to ensure that IE's not busy with other tasks when I try to navigate away from the page. That should stop anything in the page from being able to block our attempt to navigate elsewhere.
Other Notes
The change from a function to a cmdlet doesn't make much difference; it's just my preferred approach.
Passing in a list of URLs to the function rather than hardcoding the 2 URLs means I can change the URLs easily (e.g. by reading in from a file), and I'm not restricted to 2 files/sites.
Details of the methods and properties available in IE are listed here: https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa752084(v=vs.85)
Apologies that I can't explain why you're seeing the issue you are; I just hope that this amended version avoids the issue you're seeing, and a few other potential issues you've not yet seen.

InternetExplorer.Application ReadyState is always blank

I have this very simple snippet from my script:
$ie = New-Object -ComObject InternetExplorer.Application
$ie.visible = $true
$ie.navigate("https://10.0.0.1/ccmadmin/phoneFindList.do")
while ($ie.readyState -ne 4) { Start-Sleep -Milliseconds 100; Write-Host $ie.readyState }
I have definitely used very similar code before with no issues, however for some reason when I run this script, $ie.readyState just contains a blank value, and so prints an empty string to the console, and it never gets set to 4 (or anything else for that matter.
I can see that the page has a cert error, but I'm pretty sure this should still work.
What could possibly cause this issue?
When stepping through the script, I can see that readyState contains a value of 0 all the way up til line 3, where we call navigate - at this point readyState is set to an empty string or blank...
When you look at all the API info on the underlying IHTMLXMLHttp* info on MSDN and W3C sites, there is no indication of SSL restriction in use. Do a trace on the request and see property it is getting hung on.
READYSTATE_UNINITIALIZED (0)
The object has been created, but not initialized (the IHTMLXMLHttpRequest::open method has not been called).
READYSTATE_LOADING (1)
A request has been opened, but the IHTMLXMLHttpRequest::send method has not been called.
READYSTATE_LOADED (2)
The IHTMLXMLHttpRequest::send method has been called. No data is available yet.
READYSTATE_INTERACTIVE (3)
Some data has been received; however, IHTMLXMLHttpRequest::responseText is not available.
READYSTATE_COMPLETE (4)
All the data has been received.

What is the difference between the Throw keyword and the ThrowTerminatingError method in Powershell? [duplicate]

I'm trying to get my script to stop if it hits an error, and use try... catch to give me an easy way to handle the error. The easiest thing in the world I'd have thought, but I'm obviously doing something stupid. I have read for a couple of hours and I'm stuck, any help would be very handy, thanks!
Here's some example code, I've put erroraction all over the place, can't seem to stop the damn thing!
$ErrorActionPreference = "Stop"
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
}
write-output "try-catch finished, script is continuing"
this text added next day *
Fantastic answers from people, I wish I could choose more than 1 answer or had enough reputation to vote for the ones which I reluctantly didn't choose as the answer, or say thanks somehow!
The whole idea of a try/catch control is that you tell the script what to do if it encounters a terminating error, instead of the default action of throwing the error and stopping the script. If your catch block just displays a message to the terminal with Write-Host, that's all the error handling there will be, and the script will continue from there. If you think about it, it would partially defeat the purpose of try/catch if the script were stopped automatically whenever an error is caught.
In the catch block, $_ will be set to the ErrorRecord object representing the terminating error from the try block (the same one that gets stored in $error[0]). So the simplest way to end the script is to rethrow the error that would have been thrown if you hadn't used a try/catch:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
# Custom action to perform before terminating
throw $_
}
Or, if you want to display a custom message instead of the default ErrorRecord:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
throw 'Custom error message'
}
Or you could use break as suggested in Joost's answer if you want to just quit after you're finished with your custom error handling without throwing an error to the error stream.
Or you could get more sophisticated and create your own ErrorRecord object. There's a lot you can do with that, it's too big a topic to cover comprehensively here, but you can get more info about the syntax by googling System.Management.Automation.ErrorRecord. Here's an example from one of my scripts to get you started (from a function that executes a SQL query defined in the $query variable against a SQL Server database):
} catch {
$ErrorRecord = New-Object System.Management.Automation.ErrorRecord(
(New-Object Exception("Exception executing the query: $($_.Exception.InnerException.Message)")),
$query,
[System.Management.Automation.ErrorCategory]::InvalidArgument,
$null
)
$ErrorRecord.CategoryInfo.Reason = $_.CategoryInfo.Reason;
$ErrorRecord.CategoryInfo.Activity = $_.InvocationInfo.InvocationName;
$PSCmdlet.ThrowTerminatingError($ErrorRecord);
}
A couple of notes:
You'll see that in creating my custom ErrorRecord, I'm using $_, which I just said contains the ErrorRecord object associated by the terminating error that was caught in the try block. The idea is to customize some of the error output, while using parts of the default ErrorRecord by assigning them to the corresponding properties for the custom ErrorRecord.
$PSCmdlet is only available if you declare [CmdletBinding()] at the beginning of the function or script. Otherwise, you can just use throw $ErrorRecord to throw your custom error. However, the result will be more Cmdlet-style if you use $PSCmdlet.ThrowTerminatingError. (throw will spit back the line from the function that generated the error, whereas $PSCmdlet.ThrowTerminatingError will give you the line from the calling context where the function was used. It's hard to describe in a way that makes sense without getting too elaborate, but if you experiment with it you'll see what I mean.)
BTW, it's redundant to set $ErrorActionPreference = "Stop", and then use -ErrorAction Stop. The preference variable sets the default action for all cmdlets, and the -ErrorAction switch overrides the default action for a particular cmdlet, so there's no need to first specify the default, then use -ErrorAction to specify the same action you just set as the default. What you probably want to do is just leave out $ErrorActionPreference = "Stop".
The only thing missing is your break-statement in the Catch-block. Powershell won't stop the script if you don't instruct it to.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
write-output "try-catch finished, script is continuing"
And a small addendum, in case it helps you: with finally, you can add some lines of code that are always executed, regardless of wether an exception was thrown or not.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
finally {
#do some stuff here that is executed even after the break-statement, for example:
Set-Content -Path "f:\GarbageFileName.txt" -Value $null
}
#the line below is executed only if the error didn't happen
write-output "try-catch finished, script is continuing"
Try-Catch will catch an exception and allow you to handle it, and perhaps handling it means to stop execution... but it won't do that implicitly. It will actually consume the exception, unless you rethrow it. But your issue is simpler than that -- the try block takes precedence over the -ErrorAction stop in your get-content cmdlet. So instead of stopping execution, you get taken to the Catch block and continue on because there is no error handling in the catch block.
Try removing the try-catch logic from your script, and allow the cmdlet to error out:
get-content "c:\GarbageFileName.txt" -ErrorAction stop
write-output "You won't reach me if GarbageFileName doesn't exist."
And you should get the desired result of execution not reaching write-output:
PS C:\> .\so-test.ps1
Get-Content : Cannot find path 'C:\GarbageFileName.txt' because it does not exist.
At C:\so-test.ps1:2 char:12
+ get-content <<<< "c:\GarbageFileName.txt" -ErrorAction stop
+ CategoryInfo : ObjectNotFound: (C:\GarbageFileName.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

Properly displaying a tray balloon tooltip in PowerShell

Short version: I think I need help with properly using events in PowerShell that are invoked as a result of a Windows Message to get rid of a balloon tooltip's icon.
Long Version:
I have a long-running PowerShell command (a build) that I would like to be notified when it completes via a balloon tooltip in the system tray/notification area.
I was able to create a Write-BalloonTip script (below) that does roughly what I want. The only problem is that, as sometimes happens with tray icons, the tray icon doesn't disappear until I mouse over it. By re-using the same global variable to represent the NotifyIcon, I'm able to re-use this script and keep it so that only one system tray icon remains (until I mouse over it). This still feels like a hack. I tried to add an event handler so that it'd be notified on the BalloonTipClosed event and then dispose of it there. In the event handler, I tried all three techniques I've seen suggested for getting rid of the lingering icon to no avail.
The annoying part is that a simple .Dispose seems to work on subsequent calls of the script, leading me to think that the event script block isn't being called at all.
I've verified that BalloonTipClosed gets called after the tip fades away in a separate WinForms app.
Am I missing something basic? Any help is much appreciated. Thanks!
Here's the code for "Write-BalloonTip.ps1":
param
(
$text,
$title = "",
$icon = "Info",
$timeout=15000
)
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | out-null
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null
if ($global:writeBalloonTipIcon)
{
# This gets rid of the previous one
$global:writeBalloonTipIcon.Dispose()
}
$global:writeBalloonTipIcon = new-object System.Windows.Forms.NotifyIcon
$global:writeBalloonTipIcon.Icon = [System.Drawing.SystemIcons]::Information
# FIXME: This *should* cleanup the icon after it's done, but it doesn't seem to work
$global:writeBalloonTipIcon.add_BalloonTipClosed(
{
# this *should* work, but it's not. What am I missing?
$global:writeBalloonTipIcon.Icon = $null;
$global:writeBalloonTipIcon.Visible = $false;
$global:writeBalloonTipIcon.Dispose();
});
$global:writeBalloonTipIcon.Visible = $true;
$global:writeBalloonTipIcon.ShowBalloonTip($timeout, $title, $text, $icon);
I think you need to execute this code in an STA thread. PowerShell (v2 shown here) executes in an MTA thread by default:
PS U:\> [System.Threading.Thread]::CurrentThread
ManagedThreadId : 5
ExecutionContext : System.Threading.ExecutionContext
Priority : Normal
IsAlive : True
IsThreadPoolThread : False
IsBackground : False
ThreadState : Running
ApartmentState : MTA
CurrentUICulture : en-US
CurrentCulture : en-US
Name : Pipeline Execution Thread
I would recommend using the Register-ObjectEvent to subscribe to the BalloonTipClosed event. This came up recently in another SO post. Check it out.