InternetExplorer.Application ReadyState is always blank - powershell

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.

Related

Powershell StreamReader - how to wait for a new file to be readable

My script generally assumes the existence of a *.txt file with settings to help it function better. However, if the script doesn't exist, it creates a local file to hold these settings. I realise there's no logical need to then read this file, but I'd like to understand why I can't.
[void][System.IO.File]::Create($PSFileName)
$ReadPS = New-Object System.IO.StreamReader($PSFileName)
Immediately after the script may (rarely) create the file, it attempts to read it, which generates the following error: New-Object : Exception calling ".ctor" with "1" argument(s): "The process cannot access the file 'C:\Temp\MyFile.txt' because it is being used by another process."
So I have to wait for the file to be available, right? Yet a simple start-sleep for 5s doesn't work. But if I wrap it in a loop with a try-catch, it works within a fraction of a second every time:
[void][System.IO.File]::Create($PSFileName)
$RCount = 0 # if new file created, sometimes it takes a while for the lock to be released.
Do{
try{
$ReadPS = New-Object System.IO.StreamReader($PSFileName)
$RCount+=100
}catch{ # if error encountered whilst setting up StreamReader, try again up to 100 times.
$RCount++
Start-Sleep -Milliseconds 1 # Wait long enough for the process to complete. 50ms seems to be the sweet spot for fewest loops at fastest performance
}
}Until($RCount -ge 100)
$ReadPS.Close()
$ReadPS.Dispose()
This is overly convoluted. Why does the file stay locked for an arbitrary length of time that seems to increase the more I wait for it? Is there anything I can adjust or add between the file creation and the StreamReader to ensure the file is available?
As it was already mentioned in the comments, the method you are using does create a lock on the file, which stays until you call the close / dispose method or the powershell session end.
That's why the more you wait for it, the longer your session stays open and the longer the lock on the file is maintained.
I'd recommend you to just use New-Item instead which is the Powershell native way to do it.
Since you are creating a StreamReader object though, don't forget to close / dispose the object once you are over.
New-Item -Path $PSFileName -ItemType File
$ReadPS = New-Object System.IO.StreamReader($PSFileName)
#Stuff
$ReadPS.Close()
$ReadPS.Dispose()
Finally, if for some reason you still wanted to use [System.IO.File]::Create($PSFileName), you will also need to call the close method to free the lock.
You have simply to close the file handle. Try:
$fh = [System.IO.File]::Create($PSFileName)
[void]$fh.Close()
[void]$fh.Dispose()
$ReadPS = New-Object System.IO.StreamReader($PSFileName)
The Create method returns a FileStream object. Since StreamReader is derived from Stream, My Solution was to recast as astream reader. Almost a one-liner...:
$PSFileName = 'c:\temp\testfile.txt'
$Stream = [System.IO.StreamReader][System.IO.File]::Create($PSFileName)
OR, Suggestion From Jeroen Mostert :
$PSFileName = 'c:\temp\testfile.txt'
$Stream = [System.IO.StreamReader]::New( [System.IO.File]::Create($PSFileName) )
You don't have to worry about Garbage Collection with this approach because the resulting object is referenced to the variable...
Honestly I'm not too sure about this, I believe the FileStream object can be leveraged directly to read & write, but I'm less familiar than I am with StreamReader & Writer objects, so if it were me I'd do the re-cast so I can move on, but research further later.
Also, if you use another approach I would use .CLose() instead of .Dispose(). My understanding based on the .Net documentation is close is more thorough, and calls Dispose internally anyhow...

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.

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

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.

powershell - How to wait for user input

I am trying to prevent users from shutting down the computer in certain situations. I am displaying a confirm message to do that. This is how my script looks like:
$sysevent = [microsoft.win32.systemevents]
Register-ObjectEvent -InputObject $sysevent -EventName "SessionEnding" -Action $OnShutdown -SourceIdentifier "ExecuteOnShutdown"
$OnShutdown =
{
Write-Host -ForeGround Green $event.SourceEventArgs.Reason
$OUTPUT= [System.Windows.Forms.MessageBox]::Show("Do you really want to shutdown the computer?." , "confirm" , 4)
Write-Host $OUTPUT
}
This works fine but i dont know how do i suspend the shutdown command till user clicks "yes" or "no". Is there a way to prevent the system shutdown and wait for the user to click "yes" or "no" and then shutdown the server based on the answer?
In your event handler scriptblock there are a number of automatic variables defined one of which is $EventArgs. In this case there will be a Cancel property on this object you can set to $true but the docs warn:
When set to true, this property requests that the session continue to
run. It provides no guarantee that the session will not end.
There is also another variable defined in this context - $Sender. Execute man about_automatic_variables for more info.
Consider deploying your script via group policy or a local policy shutdown / logoff script which should prevent shutdown until your condition is met. You might need to wrap your messagebox call in conditional sleep loop (which is what I did for something similar in VBScript years ago!), maybe not.
If you choose to use this method, you may also want to include a preferred default selection for your messagebox (perhaps after a specified timeout period has elapsed?); a user may not hang around to see your mesaagebox as it will be drawn after the interactive desktop has unloaded.
Here's a link to a Technet article about how to Use Startup, Shutdown, Logon, and Logoff Scripts.
I'm not sure if this answers your question as this won't prevent the shutdown, it just stops it until your condition is met.

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.