How can I hide the progress display of Invoke-WebRequest? I do a lot of successive requests and have my own Write-Progress display that I use, so I don't need the built-in one popping up underneath it every time.
I use the mshtml results (the IE COM object) that are created from the result of Invoke-WebRequest automatically, so I can't switch to a WebClient or something like that, unless someone provides instructions on how to get an mshtml object from a WebClient request.
Use the $progressPreference variable. It should have a value of 'Continue' by default unless you've edited it elsewhere, which tells Powershell to display the progress bar. Since you mentioned that you have your own custom progress displays, I would reset it immediately after the cmdlet is executed. For example:
$ProgressPreference = 'SilentlyContinue' # Subsequent calls do not display UI.
Invoke-WebRequest ...
$ProgressPreference = 'Continue' # Subsequent calls do display UI.
Write-Progress ...
More info on preference variables at about_preference_variables. Here's the entry for $ProgressPreference:
$ProgressPreference
-------------------
Determines how Windows PowerShell responds to progress updates
generated by a script, cmdlet or provider, such as the progress bars
generated by the Write-Progress cmdlet. The Write-Progress cmdlet
creates progress bars that depict the status of a command.
Valid values:
Stop: Does not display the progress bar. Instead,
it displays an error message and stops executing.
Inquire: Does not display the progress bar. Prompts
for permission to continue. If you reply
with Y or A, it displays the progress bar.
Continue: Displays the progress bar and continues with
(Default) execution.
SilentlyContinue: Executes the command, but does not display
the progress bar.
Here is a reusable function to temporarily hide the progress of any script block and automatically restore the progress preference when the script block ends, even if an exception (script-terminating error) is thrown by the script block.
# Create an in-memory module so $ScriptBlock doesn't run in new scope
$null = New-Module {
function Invoke-WithoutProgress {
[CmdletBinding()]
param (
[Parameter(Mandatory)] [scriptblock] $ScriptBlock
)
# Save current progress preference and hide the progress
$prevProgressPreference = $global:ProgressPreference
$global:ProgressPreference = 'SilentlyContinue'
try {
# Run the script block in the scope of the caller of this module function
. $ScriptBlock
}
finally {
# Restore the original behavior
$global:ProgressPreference = $prevProgressPreference
}
}
}
Usage example:
Invoke-WithoutProgress {
# Here $ProgressPreference is set to 'SilentlyContinue'
Invoke-WebRequest ...
}
# Now $ProgressPreference is restored
Write-Progress ...
Notes:
The New-Module call is there so the script block passed to Invoke-WithoutProgress doesn't run in a new scope (allowing it to directly modify surrounding variables, similar to ForEach-Object's script block). See this answer for more information.
Related
As the title says, how can I bring a Powershell GUI window in front of another window after an event has happened, if it is at all possible? As in, I have, for example, Firefox opened and the Powershell GUI is running behind it, after certain event happens inside of the Powershell it pops in front of the Firefox?
On Windows, you can use [Microsoft.VisualBasic.Interaction]::AppActivate() to reactivate your own process' main window via the process ID, as reflected in the automatic $PID variable:
# Enable cross-process window activation (see below).
(Add-Type -ErrorAction Stop -PassThru -Namespace "Random.Ns$PID.AllowWindowActivation" -Name WinApiHelper -MemberDefinition #'
[DllImport("user32.dll", EntryPoint="SystemParametersInfo")]
static extern bool SystemParametersInfo_Set_UInt32(uint uiAction, uint uiParam, UInt32 pvParam, uint fWinIni);
public static void AllowWindowActivation()
{
if (! SystemParametersInfo_Set_UInt32(0x2001 /* SPI_SETFOREGROUNDLOCKTIMEOUT */, 0, 0 /* timeout in secs */, 0 /* non-persistent change */)) {
throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error(), "Unexpected failure calling SystemParametersInfo() with SPI_SETFOREGROUNDLOCKTIMEOUT");
}
}
'#)::AllowWindowActivation()
# Load the required assembly.
Add-Type -AssemblyName Microsoft.VisualBasic
# Launch a sample GUI application that will steal the focus
# (will become the foreground application).
Start-Process notepad.exe
# Wait a little.
Start-Sleep 3
# Now reactivate the main window of the current process.
[Microsoft.VisualBasic.Interaction]::AppActivate($PID)
Note:
Programmatic activation of arbitrary windows across process boundaries is prevented by default:
Instead of the targeted window getting activated, its taskbar button flashes, so as to signal to the user the intent to make the window active.
The Add-Type -MemberDefinition call above overrides this for the current session using a P/Invoke call to the SystemParametersInfo WinAPI function, via setting its SPI_SETFOREGROUNDLOCKTIMEOUT parameter to 0.
This will incur a one-time compilation performance penalty per session.
Cross-process window activation will be enabled for the remainder of the session, for all processes.
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.
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.
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.
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.