I have two processes that reads from the same file. I want to make sure that those two processes doesn't run at the same. My approach is that , when a process starts, it should look for a file(process.txt). If the file doesn't exist, it creates the file and continue execution, reading from the shared file(sharedfile.txt) between the two processes. It then deletes the process.txt file after executing.
Since both process deletes process.txt file after execution, if the file exists,it should go into a start sleep until the other process finishes and deletes the file.
The problem here is when one process finishes and deletes the file(process.txt), the other still stay in the loop without executing even if no file exists. I am not sure of the right loop to use. I tried a couple of them and couldn't achieve my goal.
Clear Host
$sleeptime = 15
$lockfile = "C:\Users\processid.txt"
$file = test-path -Path $lockfile
Try
{
if($file -eq 'True')
{
echo “Lock file found!”
echo "This means file is being used by another process"
echo "Wait for file to be deleted/released"
echo “Sleeping for $sleeptime seconds”
Start-Sleep $sleeptime -Verbose
}
else
{
New-item -Path $lockfile
#Executing a code here
}
Remove-Item $lockfile –Force
}
}
Catch
{
Write-Host $_.Exception.Message`n
}
Consider this one, it creates a lock file and opens it for exclusive access. Let's you run your script, then cleans up afterwards. If access cannot be gained it waits 5 seconds before trying again.
Using this for both scripts should make them play nice and wait for their turn. If for some reason a lock file gets left behind (unplanned reboot that aborts script or similar) this one will still work fine as well.
$LockFile = 'C:\temp\test.lock'
# Loop that runs until we have exclusive write access to $LockFile
while ($FileStream.CanWrite -eq $false) {
if (-not (Test-Path -Path $LockFile)) {
Set-Content -Path $LockFile -Value 'Lockfile'
}
try {
$FileStream = [System.IO.File]::Open("C:\temp\test.lock",'Open','Write')
}
catch {
'Waiting 5 seconds'
Start-Sleep -Seconds 5
}
}
# ---
# Your actual script here
# ---
# Cleanup
$FileStream.Close()
$FileStream.Dispose()
Remove-Item -Path $LockFile -Force
You could also go one step further and instead lock the file you're actually using, but then you'll need to read it from the file stream since it will be locked for any cmdlets.
You could use a while loop like this
Clear Host
$sleeptime = 15
$lockfile = "C:\Users\processid.txt"
While(Test-Path -Path $lockfile){
Write-Host "Lock file found!"
Write-Host "This means file is being used by another process"
Write-Host "Wait for file to be deleted/released"
Write-Host "Sleeping for $sleeptime seconds"
Start-Sleep $sleeptime -Verbose
}
try{
New-item -Path $lockfile
#Executing a code here
}
catch{
Write-Host $_.Exception.Message`n
}
finally{
Remove-Item $lockfile –Force -ErrorAction SilentlyContinue
}
Related
When I use start-process to create a new process and assign it to a variable...
$np = start-process -passThru notepad
... and then query .MainWindowHandle ...
$np.MainWindowHandle
... I seem to be given the HWND of the notepad.
However, when I try to do the same thing in one go...
(start-process -passThru notepad).MainWindowHandle
... I am given 0.
This is probably the case because MainWindowHandle is evaluated before notepad has created its window.
So, is there a way, without using start-sleep or going into a loop that repeadetly queries the value of MainWindowHandle, to wait until notepad is done starting up?
So, is there a way,
Yes
without using start-sleep or going into a loop that repeadetly queries
the value of MainWindowHandle
Not that I can think of :)
# Define a timeout threshold 10 seconds into the future
$threshold = (Get-Date).AddSeconds(10)
# Start the process
$proc = Start-Process notepad -PassThru
while(-not $proc.HasExited -and ((Get-Date) -lt $threshold -or $proc.MainWindowTitle -eq 0)){
Start-Sleep -MilliSeconds 250
}
if($proc.MainWindowTitle -eq 0){
if(-not $proc.HasExited)
$proc.Terminate()
}
throw 'Failed to spawn window in time'
return
}
# Do stuff with $proc.MainWindowHandle
Have a command that (on 2008 boxes) throws an exception due to the buffer limit.
Essentially, the script stops WAUA, blows out SoftwareDistribution, then reaches out to rebuild SD and check for updates against WSUS then checks back in so the alerts in WSUS stop.
I want a specific line to retry if an exception is thrown until it finishes without an exception, then we can tell it to report back in to WSUS to clear it out.
Stop-Service WUAUSERV
Remove-Item -Path C:\WINDOWS\SoftwareDistribution -recurse
Start-Service WUAUSERV
GPUpdate /force
WUAUCLT /detectnow
sleep 5
## This is the command I'd like to loop, if possible, when an exception is thrown ##
$updateSession = new-object -com "Microsoft.Update.Session"; $updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates
WUAUCLT /reportnow
Any kind of help would be appreciated. Everything I've been able to find has been how to create my own exception but not how to handle it when an exception is thrown and retry until it finishes without an error.
Edit:
Then based on the below Answer, is this how I would want it to be written so it will continue to check until it runs successfully and then it'll report back in?
Stop-Service WUAUSERV
Remove-Item -Path C:\WINDOWS\SoftwareDistribution -recurse
Start-Service WUAUSERV
GPUpdate /force
WUAUCLT /detectnow
sleep 5
while(-not $?) {$updateSession = new-object -com "Microsoft.Update.Session"; $updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates}
WUAUCLT /reportnow
You can use the special character $? This will return false if the last command returned with error, so your while loop would just look like:
while(-not $?)
See what is $? in powershell.
Alternatively, $error[0] gives the last thrown error message so you could build a while loop around that, similar to:
while($error[0] -ne "error message")
Set a value to false and only flip it if you get total success. Loop until you succeed or your timeout exceeds your defined value -- if you don't include a timeout, you have created an infinite loop condition where if the host never succeeds, it'll run until reboot.
$sts = $false
$count = 0
do {
try {
$updateSession = new-object -com "Microsoft.Update.Session"
$updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates
$sts = $true
} catch {
## an exception was thrown before we got to $true
}
$Count++
Start-Sleep -Seconds 1
} Until ($sts -eq $true -or $Count -eq 100)
I am working on a PS script to generate .xml representations of a huge number of Crystal Reports (Windows 7). In this script, I create an object representing all the files that need to be parsed, then loop over them, calling an .exe on them one-by-one. Occasionally, this .exe crashes. This is fine because it's pretty rare and the reports that can't be processed can be flagged and reviewed manually. The problem is that I have thousands of .rpt files to process, and when the .exe crashes, Windows pops up a dialog asking to debug or continue.
Things I have tried in order to solve the issue:
HKEY_CURRENT_USER\Software\Microsoft\Windows\Windows Error Reporting\DebugApplications: I put my exe name here and set the value to 0 (don't debug)
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Windows Error Reporting\DebugApplications: same as above
Set the loop that calls the exe to SilentlyContinue
Turn off error reporting as follows: Control Panel > Action Center > Change Action Center Settings > Problem Reporting Settings > Change report settings for all users > "Never Check for Solutions" > OK > OK (This only disables the "Windows can check online..." dialog)
Still, I'm getting the popup. There is another reg key, which disables the "Program has stopped working" UI entirely, but I don't want to do that because as a developer of other applications, I need to know when things crash. I just want to exclude this script or the exe it calls from showing the UI.
If I can do that, then the script can run unattended.
The .exe that misbehaves is the latest binary release from here: https://github.com/ajryan/RptToXml and seems to fail when it encounters a null byte in the report file.
Here is my code:
[xml]$MainConfigFile = Get-Content "settings.config.xml"
[xml]$SSRSConfigFile = Get-Content "ssrs.config.xml"
[xml]$CrystalConfigFile = Get-Content "crystal.config.xml"
# create settings objects from xml objects
$MainSettings = #{
OutputPath = $MainConfigFile.Settings.OutputPath
SearchString = $MainConfigFile.Settings.SearchString
ParseCrystal = $MainConfigFile.Settings.ParseCrystal
ParseSSRS = $MainConfigFile.Settings.ParseSSRS
}
$CrystalSettings = #{
InputFolderPath = $CrystalConfigFile.CrystalSettings.InputFolderPath
ContinueOnError = $CrystalConfigFile.CrystalSettings.ContinueOnError
}
$RsSettings = #{
ReportServerUri = $SSRSConfigFile.RsSettings.ReportServerUri
RsVersion = $SSRSConfigFile.RsSettings.RsVersion
}
Clear
Write-Host "Ensure these settings are correct before you proceed:" -ForegroundColor Yellow
Write-Host ""
Write-Host "Main Settings" -ForegroundColor Green
$MainSettings
Write-Host ""
Write-Host "Crystal Settings" -ForegroundColor Green
$CrystalSettings
Write-Host ""
Write-Host "SSRS Settings" -ForegroundColor Green
$RsSettings
Write-Host ""
# user must confirm
[string]$SettingsOK=(Read-Host "[Y] to proceed, [N] to quit:")
if ($SettingsOK -ne "Y") {exit}
Write-Host ""
Write-Host "______________________________________"
Clear
# is the output path syntax valid?
if (!(Test-Path -Path $MainSettings.OutputPath -IsValid)) {
Write-Host -ForegroundColor Green "Output path syntax is invalid:" $MainSettings.OutputPath
exit
} else {
Write-Host -ForegroundColor Green "Output path syntax is correct:" $MainSettings.OutputPath
}
# does the output path exist?
if (!(Test-Path -Path $MainSettings.OutputPath)) {
Write-Host -ForegroundColor Yellow "Output path does not exist:" $MainSettings.OutputPath
[string]$CreateOutputPathOK=(Read-Host "[Y] to create the directory, [N] to quit.")
if ($CreateOutputPathOK -ne "Y") {exit} else {New-Item -Path $MainSettings.OutputPath -ItemType Directory}
} else {
Write-Host -ForegroundColor Green "Output path already exists:" $MainSettings.OutputPath
}
Write-Host ""
Write-Host "______________________________________"
Clear
# get all .rpt files in the input folder, recursively
$CrystalFiles=Get-ChildItem -Path $CrystalSettings.InputFolderPath -Include "*.rpt" -Recurse
Write-Host ""
# count files first and ask the user if they want to see the output, otherwise proceed
Write-Host -ForegroundColor Yellow $CrystalFiles.Count ".rpt files were found in" $CrystalSettings.InputFolderPath
[string]$ShowFilesOK=(Read-Host "[Enter] to proceed, [Y] to view the list of files in the directory, [N] to quit.")
if ($ShowFilesOK -eq "Y") {
Clear
# loop through the collection of files and display the file path of each one
$CrystalFiles | ForEach-Object -Process {$_.FullName.TrimStart($CrystalSettings.InputFolderPath)}
Write-Host "______________________________________"
# user must confirm
Write-Host ""
Write-Host -ForegroundColor Yellow "The above .rpt files were found in" $CrystalSettings.InputFolderPath
} elseif ($ShowFilesOK -eq "N") {exit}
Write-Host ""
[string]$ProcessingOK=(Read-Host "[Y] to proceed with .rpt file processing, [N] to quit:")
if ($ProcessingOK -ne "Y") {exit}
Write-Host ""
Write-Host "______________________________________"
Clear
# create a dir inside the output path to hold this run's output
Write-Host -ForegroundColor Green "Creating folder to hold this run's output..."
$RunDir = (New-Item -Path $MainSettings.OutputPath -Name "$(Get-Date -f yyyy-mm-dd__hh_mm_ss)" -ItemType Directory)
$RunDir.FullName
# use .NET ArrayList because immutable PS arrays are very slow
$Success = New-Object System.Collections.ArrayList
$Failure = New-Object System.Collections.ArrayList
#loop through the collection again, this time processing each file and dumping the output to the output dir
$CrystalFiles | ForEach-Object -ErrorAction SilentlyContinue -Process {
$RelativePathName = $_.FullName.TrimStart($CrystalSettings.InputFolderPath)
$XmlFileName = "$RunDir\$RelativePathName.xml"
# force-create the file to ensure the parent folder exists, otherwise RptToXML will crash trying to write the file
New-Item -Path $XmlFileName -Force
# then simply delete the empty file
Remove-Item -Path $XmlFileName
Write-Host -ForegroundColor Green "Processing file" $RelativePathName
CMD /c .\RptToXML\RptToXml.exe $_.FullName $RunDir\$($_.FullName.TrimStart($CrystalSettings.InputFolderPath)).xml
if ($LASTEXITCODE -eq 0) {
Write-Host "Success" $Success.Add($RelativePathName)} else {Write-Host "Failure" $Failure.Add($RelativePathName)}
}
$Success | Export-CSV "$RunDir\CrystalSuccess.txt"
$Failure | Export-CSV "$RunDir\CrystalFailure.txt"
I hate to do this, answering my own question, but I've found a workaround for now.
Before the loop:
# Disable WER temporarily
Set-ItemProperty "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 1
After the loop:
# Reset the WER UI reg key
Set-ItemProperty "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 0
This will be improved by calling this script from another script:
Get the current value of the key(s) to be modified
Change them
Call the script which returns control to the caller even if it crashes
Return the reg keys to their original value
Having a bit of trouble with the Azure Custom script extension on a windows VM. I have created a script that installs a particular business application. In order to accomplish this, the script calls several other scripts and a reboot is required in between two scripts.
The script is designed such that it can "pick up where it left off" by checking whether certain files exist or if registry keys exist. The reboot portion of this goes fine, however, I'm noticing that the script handler sets several registry keys to keep track of its state in
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Azure\ScriptHandler
I see a registry value called "MreSeqNumStarted", which I presume is how the script handler keeps track of what script it's executing. There is only one script fired by the extension currently, and this value is set to 0.
What I'm seeing, is that, when the machine is rebooted after the script has completed, the script fires again. This occurs with every reboot of the virtual machine. I believe the script extension is not marking the script as "completed", and I'm somewhat at a loss as to why. The basic (sanitized) flow of the script is below:
trap {$_.Exception.Message; Get-PSCallstack;continue}
if(Test-Path C:\ScriptLog.txt)
{
Add-Content "C:\ScriptLog.txt" "`nScript Previously Run, Prior log exists."
} else {
Write-Host "Begin Script" | Out-File "C:\ScriptLog.txt"
}
#$storagePath = [uri]::EscapeDataString($storagePath)
#Create Directory Structure and download scripts/files
if(Test-Path C:\PreparePlatform\) {
Add-Content "C:\ScriptLog.txt" "`nFiles Already Downloaded... Continuing"
} else {
New-Item -ItemType Directory -Force -Path C:\SoftwareName\PreparePlatform\
(New-Object Net.WebClient).DownloadFile('SAS-URL-GOES-HERE','C:\SoftwareName\PreparePlatform\PreparePlatform.zip');(new-object -com shell.application).namespace('C:\SoftwareName\PreparePlatform').CopyHere((new-object -com shell.application).namespace('C:\SoftwareName\PreparePlatform\PreparePlatform.zip').Items(),16)
(New-Object Net.WebClient).DownloadFile('SAS-URL-GOES-HERE','C:\SoftwareName\SOMEZIP.zip');(new-object -com shell.application).namespace('C:\SoftwareName').CopyHere((new-object -com shell.application).namespace('C:\SoftwareName\SOMEZIP.zip').Items(),16)
Add-Content "C:\ScriptLog.txt" "`nExtracted Install Media"
}
if(Test-Path C:\SoftwareName\preparedone.txt) {
Add-Content "C:\ScriptLog.txt" "`nPreparePlatform already completed... Continuing"
} else {
Invoke-Expression "Set-Location c:\SoftwareName\PreparePlatform\; C:\SoftwareName\PreparePlatform\Prepare-WindowsPlatform.ps1"
CreateSQLUser -identifierValue $value -saPassword $sqlSaPassword -tenantPass $TenantSqlPassword
CreateSQLLogin -identifierValue $value -saPassword $SqlSaPassword -tenantPass $TenantSqlPassword
Write-Host "PreparePlatform Complete" | Out-File "C:\SoftwareName\preparedone.txt"
}
if(Test-Path C:\SoftwareName\UpdatePlatform.txt) {
Add-Content "C:\ScriptLog.txt" "`nUpdatePlatform already completed... Continuing"
} else {
Add-Content "C:\ScriptLog.txt" "`nBegin Update-Platform"
Invoke-Expression "Set-Location C:\SoftwareName\PreparePlatform\; C:\SoftwareName\PreparePlatform\Update-WindowsPlatform.ps1"
Write-Host "UpdatePlatform Complete" | Out-File "C:\SoftwareName\UpdatePlatform.txt"
}
if(Test-Path C:\SoftwareName\Rebooted.txt) {
Add-Content "C:\ScriptLog.txt" "`nReboot already completed... Continuing"
} else {
Write-Host "Rebooting" | Out-File "C:\SoftwareName\Rebooted.txt"
Restart-Computer -Force
Restart-Computer -Force
Start-Sleep 3600 #Make sure nothing else executes
}
# TODO: Make more robust for potentially more nodes... need to delay 1 hour for each node
If($env:computername -eq 'pvm-SoftwareName-1') { Write-Output "Second Node, waiting 1 hour"; Add-Content "C:\ScriptLog.txt" "`nNot First Node, waiting 1 hour" ;Start-Sleep -Seconds 3600 }
Add-Content "C:\ScriptLog.txt" "`nPrepare SoftwareName Installers"
Set-Location C:\SoftwareName\Script\; C:\SoftwareName\Script\Prepare-SoftwareNameInstallers.ps1 -neededParameters $values
Add-Content "C:\ScriptLog.txt" "`nPrepare SoftwareName Installers complete"
try
{
if(-not (Get-Service -Name 'SoftwareName server')) {
C:\SoftwareName\Script\Install-SoftwareNameBP.ps1 -neededParameters $values
} else {
Add-Content "C:\ScriptLog.txt" "`n ALREADY INSTALLED!"
}
}
catch
{
$_.Exception.Message;Get-PSCallStack
if (-not (Get-Service -Name 'SoftwareName Server').Status -eq 'Running')
{throw "Service unavailable"}
}
Write-Output "Install Finished."
try
{
if(-not ((Invoke-WebRequest -Uri https://IISSiteURL/).StatusCode -eq '200'))
C:\SoftwareName\Script\Install-SoftwareNameSF.ps1 -neededParameters $values
}
catch
{
$_.Exception.Message;Get-PSCallStack
if(-not (Test-Path -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{AGUID}") )
{throw "Smartforms Install failed"}
}
try
{
#Begin
C:\SoftwareName\Script\Install-SoftwareNameSP.ps1 -neededParameters $values
}
catch
{
$_.Exception.Message;Get-PSCallStack
if(-not (Test-Path -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{AGUID}") )
{throw "Sharepoint Install failed"}
}
try
{
#[bool]$skipDeploy
#if($env:computername -eq 'pvm-SoftwareName-0') { $skipDeploy = $false } else { $skipDeploy = $true }
#Begin
C:\SoftwareName\Script\Install-SoftwareNameMG.ps1 -neededParameters $values
catch
{
$_.Exception.Message;Get-PSCallStack
if(-not (Test-Path -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{AGUID}") )
{throw "Management Install failed"}
}
try
{
C:\SoftwareName\Script\Install-Finalize.ps1 -neededParameters $values
}
catch
{
$_.Exception.Message;Get-PSCallStack
if(-not (Test-Path -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{AGUID}") )
{throw "Install Finalize Failed"}
}
#Kill the script
Add-Content "C:\ScriptLog.txt" "`nScript Completed"
Exit
Below is the expected output of C:\ScriptLog.txt
Begin Scheduled Job Regstration
Extracted Install Media
Begin PreparePlatform
Completed PreparePlatform
Begin Update-Platform
Script Previously Run, Prior log exists.
Files Already Downloaded... Continuing
PreparePlatform already completed... Continuing
UpdatePlatform already completed... Continuing
Reboot already completed... Continuing
Prepare SOFTWARENAME Installers
Prepare SOFTWARENAME Installers complete
C:\SOFTWARENAME\Script\Install-K2BP.ps1 -parameterValues $values
C:\SOFTWARENAME\Script\Install-K2SF.ps1 -parameterValues $values
C:\SOFTWARENAME\Script\Install-K2SP.ps1 -parameterValues $values
C:\SOFTWARENAME\Script\Install-K2MG.ps1 -parameterValues $values
C:\SOFTWARENAME\Script\Install-Finalize.ps1 -parameterValues $values
Script Completed
After a reboot, the same log file:
Begin Scheduled Job Regstration
Extracted Install Media
Begin PreparePlatform
Completed PreparePlatform
Begin Update-Platform
Script Previously Run, Prior log exists.
Files Already Downloaded... Continuing
PreparePlatform already completed... Continuing
UpdatePlatform already completed... Continuing
Reboot already completed... Continuing
Prepare SOFTWARENAME Installers
Prepare SOFTWARENAME Installers complete
C:\SOFTWARENAME\Script\Install-SOFTWARENAMEBP.ps1 -neededParameters $values
C:\SOFTWARENAME\Script\Install-SOFTWARENAMESF.ps1 -neededParameters $values
C:\SOFTWARENAME\Script\Install-SOFTWARENAMESP.ps1 -neededParameters $values
C:\SOFTWARENAME\Script\Install-SOFTWARENAMEMG.ps1 -neededParameters $values
C:\SOFTWARENAME\Script\Install-Finalize.ps1 -neededParameters $values
Script Completed
Script Previously Run, Prior log exists.
Files Already Downloaded... Continuing
PreparePlatform already completed... Continuing
UpdatePlatform already completed... Continuing
Reboot already completed... Continuing
Prepare SOFTWARENAME Installers
Prepare SOFTWARENAME Installers complete
C:\SOFTWARENAME\Script\Install-SOFTWARENAMEBP.ps1 -neededParameters $values...
Edit1: I've commented out all calls to external scripts. The script at this point only writes output to a file effectivley and traps any exceptions in doing so. The script continues to re-fire with every VM reboot.
I would like to write a batch script which will poll a windows directory for a certain time limit and pick a file as soon as it is placed in the directory.
It will also timeout after a certain time if the file is not placed in that directory within that time frame.
I would also like to parse the xml file and check for a status.
Here's a PowerShell script that will do what you asked.
$content variable will store contents of the file (it will actually be an array of lines so you can throw it into foreach loop).
$file = 'C:\flag.xml'
$timeout = New-TimeSpan -Minutes 1
$sw = [diagnostics.stopwatch]::StartNew()
while ($sw.elapsed -lt $timeout)
{
if (Test-Path $file)
{
"$(Get-Date -f 'HH:mm:ss') Found a file: $file"
$content = gc $file
if ($content -contains 'something interesting')
{
"$(Get-Date -f 'HH:mm:ss') File has something interesting in it!"
}
break
}
else
{
"$(Get-Date -f 'HH:mm:ss') Still no file: $file"
}
Start-Sleep -Seconds 5
}