I'm programmatically installing a COM+ proxy component via Powershell, using msiexec on the msi, and then using the COMAdmin.COMAdminCatalog object to set the remote server on the proxy.
The problem is that it takes a while for the newly installed proxy to be available in the "Applications" collection of COMAdminCatalog. Is there some way to force a refresh of the catalog before getting the application list?
Essentially, what I do is this:
msiexec /q /i $appName.msi
use the COMAdmin.COMAdminCatalog to enumerate the apps.
function Set-Remote-Server-For-Complus-Application($appName, $remoteServer) {
$comAdmin = New-Object -comobject COMAdmin.COMAdminCatalog
$apps = $comAdmin.GetCollection("Applications")
$apps.Populate();
$app = $apps | Where-Object {$_.Name -eq $appName}
if ($app -eq $null) {
Write-Warning "Unable to find COM+ app ""$appName""."
Return
}
$app.Value("ApplicationProxyServerName") = $remoteServer
$result = $apps.SaveChanges()
$apps = $null
if ($result -eq 1) {
Write-Output "Successfully set complus remote server ""$remoteServer"" on ""$appName"""
}
}
The problem is that the application is not Found. If I add a Start-Sleep -Seconds 2 between the calls, it works. But, sleeping is not good, because sometimes it might take longer than 2 seconds, sometimes it might only take 200 milliseconds, so the wait is unnecessarily long.
Is there any way to make sure that the COMAdmin.COMAdminCatalog is actually updated before I try to enuerate the applications, without resorting to sleeping and just hoping for the best?
Related
I am currently stuck at a problem involving a WMI Event subscriber on a remote session running in a background runspace.
Below is the representative code that reproduces the issue (the real code is too long to share here) but, essentially, through a PS script I remotely install advertised WSUS updates with reboots when necessary. It takes about 90 minutes end to end.
The issue I am trying to solve at the moment is that during the course of patching, the support staff inadvertently log in to the the server being remotely patched and do their admin activities. To remind them, I want to display a pop up message as soon as the user logs in when my remote script is running. I am trying to do it using a background runspace plugged into the main patching script. It makes use of WMI eventing on the target server (which is being patched) to monitor user logons and display message as soon as it detects one. The below code is working as I expect. It even survives target server reboots.
$RemoteServerName = 'Server1.contoso.com'
$UserLogonAlertScriptBlock = {
param ($SyncedHashTable)
$RemoteServerName = $SyncedHashTable.target
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
while($true){
if($Session.State -eq 'Opened') {
$RemoteMonitoringJob = Invoke-Command -Session $Session -AsJob -ScriptBlock {
$null = Register-WMIEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_LogonSession'" -SourceIdentifier 'User.Logon'
Wait-Event -SourceIdentifier "User.Logon" -Timeout 7200 | ForEach-Object {
msg * /TIME:7200 /V "User logon detected" | Out-Null
$_ | Remove-Event
}
}
while($RemoteMonitoringJob.State -in #('NotStarted','Running')) {
Start-Sleep -Seconds 1
}
} else {
while($true) {
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
if($Session.State -eq 'Opened') {
break
}
Start-Sleep -Seconds 1
}
}
}
}
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.runspace = $Runspace
$SyncedHashTable = [hashtable]::Synchronized(#{})
$SyncedHashTable.host = $host
$SyncedHashTable.target = $RemoteServerName
$Runspace.Open()
$handle = $PowerShell.AddScript($UserLogonAlertScriptBlock).AddArgument($SyncedHashTable).BeginInvoke()
Write-Host '$(Get-Date): Long running script execution targeting $RemoteServerName has started'
Start-Sleep -Seconds 120 # it usually runs for upto 90 minutes, with remote reboots of $RemoteServerName
Write-Host "$(Get-Date): The script execution has completed"
### The code that cleans up the sticky event subscriber on the target server needs to be added here
The part I am stuck at is after the script completes its execution. The wsmanprovhost.exe running on the target server continues to stick around and shows alert messages when new users log on. I think it's because of the WMI event listener still being active on the box, not releasing the remote PS session.
In the above code, I need help close that remote listener so wsmanprovhost.exe disappears.
Could you please help?
PS. I have referred to #mklement0 's response in the following post but still no joy: The RunSpace and its closure
Update:
I have managed to address the challenge by adding a Boolean flag into the SyncedHashtable which is passed to the background runspace. When I want to stop the remote logon monitoring, in the main script I flip the flag. Since it's inside a synced hashtable, I can monitor that inside the runspace and terminate the remote invoke command job in the run space. But I still had to forcibly kill the remote wsmprovhost.exe as it refuses to go. I could do it by getting the pid of the remote PS session in advance. Not the most elegant way to close a remote PS session but it does the job for me. It's just that since the remote session is continuously monitoring for user logon event, there does not appear to be a way to run a piece of code in that session to unsubscribe the WMI event source. Will do more testing to see if there is any side effect.
Here is a very simple example of the problem I am experiencing:
$process = Start-Process 'C:\Program Files\Mozilla Firefox\firefox.exe' -argumentlist "-new-window https://google.com -foreground" -PassThru
Write-Host $process.Id
The firefox window will start and work as expected, it will return a process id, but when I actually check the running processes, I see no results for that PID.
I tried adding this just to see,
while (-not $process.HasExited){
Write-Host "..."
}
Write-Host $process.HasExited
And it looks like the process does run for maybe a couple milliseconds before it exits.
I'm thinking this may have something to do with how Firefox handles it's own processes. Because I tested a similar setup with some other random apps and they all worked as expected.
Any ideas on how to work around this when it comes to Firefox?
There are several challenges to overcome:
The firefox process that ends up presenting the actual browser window is different from the one that is initially launched. That is, as you've observed, the launched process spawns other processes and itself exits quickly.
As Olaf points out, modern browsers typically launch multiple non-transient processes, so the challenge is how to identify the one that represent the browser window.
Browsers may reuse existing processes, so a single process can present multiple windows / tabs, and closing one of them won't terminate the process as a whole.
If you need to ensure that a dedicated, new process is used, you have two options:
(a) Make sure that no preexisting Firefox instance is running, either by erroring out, or - if acceptable for your use case - by forcefully terminating all existing instances first (Stop-Process -Name firefox).
(b) With significantly more effort, create a dedicated, temporary Firefox profile that you can launch with the -new-instance option, which allows multiple independent Firefox instances to run concurrently and whose lifetime can be tracked separately.
The following - cumbersome - solution implements option (b):
If no firefox process is found, there is no concern about creating independent instances, and Firefox can be launched normally.
Otherwise, a temporary profile is created, and launched via the -new-instance and -profile options to ensure that a new process will be used to present the new browser window.
After launching the initial process, loop until a firefox process appears that was launched later and has a nonempty window title, which is then presumed to be the real process of interest.
You can then wait for the termination of this process to know when the dedicated browser window has been closed. If a temporary profile had to be created, it is cleaned up afterwards.
# Comment this statement out to silence the verbose messages below.
$VerbosePreference = 'Continue'
$now = Get-Date
$url = 'https://example.org' # URL to open.
# Launch a (new) Firefox instance.
if ($alreadyRunning = [bool] (Get-Process -ErrorAction Ignore firefox)) {
# Determine the path for a temporary profile with a unique name.
$tempProfilePath = Join-Path ([IO.Path]::GetTempPath()) ([datetime]::utcnow.tostring('o') -replace '\D')
Write-Verbose "Firefox is already running. Creating temp. profile $tempProfilePath..."
# Note: Creating an empty directory for the profile is seemingly enough.
$null = New-Item -Type Directory $tempProfilePath
Write-Verbose "and starting a new instance with it..."
Start-Process firefox "-new-instance -profile $tempProfilePath $url"
} else {
Write-Verbose 'Firefox isn''t running. Starting normally...'
Start-Process firefox $url
}
# Find the newly launched process that is the actual browser window.
Write-Verbose 'Waiting for a recently launched Firefox process with a nonempty window title to appear...'
while (-not (
$ps = Get-Process firefox |
Where-Object StartTime -gt $now |
Where-Object MainWindowTitle
)) {
Write-Host -NoNewLine .
Start-Sleep -MilliSeconds 500
}
Write-Host
Write-Verbose "Found. Waiting for process to exit..."
$ps.WaitForExit()
Write-Verbose 'Process has exited.'
if ($alreadyRunning) {
Write-Verbose "Cleaning up temporary profile $tempProfilePath..."
do {
# The profile dir. is typically held on to for a little while longer by associated processes that may not have terminated yet.
Start-Sleep -MilliSeconds 200
Remove-Item -ErrorAction Ignore -Literalpath $tempProfilePath -Recurse -Force
}
while (Test-Path -LiteralPath $tempProfilePath)
}
Thanks to #mklement0 work, in your case you can use the parent Process ID.
I use WMI to get the parent process, but it works for the very first launch.
$parentProcess = Start-Process 'C:\Program Files\Mozilla Firefox\firefox.exe' -argumentlist "-new-window https://google.com -foreground" -PassThru
$childProcess = get-process -id $(Get-CimInstance -Class Win32_Process -Filter "Name = 'firefox.exe'" | where {$_.ParentProcessId -eq $parentProcess.id}).ProcessId
# effectively stop the child
$childProcess | Stop-Process
I'm trying to create a script that can export a user's mailbox to a PST, remotely (Exchange Server 2010 console is installed on the server we're running this from, and the module is loaded correctly). It's being done using a script so our L2 admins do not have to manually perform the task. Here's the MWE.
$UserID = Read-Host "Enter username"
$PstDestination = "\\ExServer\Share\$UserID.pst"
$Date = Get-Date -Format "yyyyMMddhhmmss"
$ExportName = "$UserID" + "$Date"
try {
New-MailboxExportRequest -Mailbox $UserID -FilePath $PstDestination -Name $ExportName -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
# Loop through the process to track its status and write progress
do {
$Percentage = (Get-MailboxExportRequest -Name $ExportName | Get-MailboxExportRequestStatistics).PercentComplete
Write-Progress "Mailbox export is in progress." -Status "Export $Percentage% complete" -PercentComplete "$Percentage"
}
while ($Percentage -ne 100)
Write-Output "$UserID`'s mailbox has been successfully exported. The archive can be found at $PstDestination."
}
catch {
Write-Output "There was an error exporting the mailbox. The process was aborted."
}
The problem is, as soon as we initiate the export, the task gets Queued. Sometimes, the export remains queued for a very long time, and the script is currently unable to figure out when the task begins, and when it does, is unable to display the progress correctly. The export happens in the background, but the script remains stuck there. So anything after the export, does not get executed, and the whole thing then has to be done manually.
Please suggest a way to handle this?
I tried adding a wait timer and then a check to see if the export has begun. It didn't quite work as expected.
Two things. First one is more about performance/hammering Exchange with unnesacary requests in do/while loop. Start-Sleep -Seconds 1 (or any other delay that makes sense depending on the mailbox size(s)) inside the loop is a must.
Second: rather than wait for job to start, just resume it yourself:
if ($request.Status -eq 'Queued') {
$request | Resume-MailboxExportRequest
}
I am trying to automate to extract data from a webpage after clicking on a button. But when I try to execute, the system freezes and it requires hard reboot. Please refer the code below.
$ie = New-Object -ComObject Internetexplorer.application
$ie.visible = $true
$ie.navigate("http://beta.speedtest.net")
while($ie.readystate -ne 4) {
sleep -Milliseconds 100
}
$link = $ie.document.getElementsByTagName("span") |Where-Object {$_.innertext -like "Begin test"}
$link.click()
Reference: Powershell website automating button click on loginenter code here
There is nothing in there which would cause a system freeze.
As you are starting an Internet Explorer instance, I guess IE is crashing your system and it is unreleated to your script or powershell.
You can verify by running the script on another machine.
Then I would try reinstalling IE on the crashing machine.
I want to check if any instance of internet explorer, ie any window of it, is already opened or not so that i can decide that a new object has to be created or i have to attach to existing process of that IE window. This is the code below:
$ie = (New-Object -COM "Shell.Application").Windows()| ? { $_.Name -eq "Windows Internet Explorer" }
I want to attach to windows() only when a window of IE is already opened and if no IE window is opened then the line should be like this:
$ie = (New-Object -COM "Shell.Application")
try:
if (
Get-Process iexplore -ea silentlycontinue |
Where-Object {$_.MainWindowTitle -ne ""}
)
if (Get-Process iexplore) { "Yes" } else { "No" }
I have no idea whether this works in a terminal services environment with many user (does a non-elevated Get-Process only look in your own session (and the system session) for processes?)