How to restart process if it crashes using powershell? - powershell

I'm trying to run Core Keeper Dedicated Server, everything seems fine, but from time to time CoreKeeperServer.exe crashes. So main Powershell script what launches it not tracking is it crashed or not. How do i need to modify this script, so it constantly (every 1-5 seconds or so) monitored if process still running or not, and start it again if it is? Also i want to keep "press q to exit" functionality what already included in stock script.
Stock script:
# Feel free to change these (see README), but keep in mind that changes to this file might be overwritten on update
$CoreKeeperArguments = #("-batchmode", "-logfile", "CoreKeeperServerLog.txt") + $args
$script:ckpid = $null
function Quit-CoreKeeperServer {
if ($script:ckpid -ne $null) {
taskkill /pid $ckpid.Id
Wait-Process -InputObject $ckpid
Write-Host "Stopped CoreKeeperServer.exe"
}
}
try {
if (Test-Path -Path "GameID.txt") {
Remove-Item -Path "GameID.txt"
}
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
# Wait for GameID
while (!(Test-Path -Path "GameID.txt")) {
Start-Sleep -Milliseconds 100
}
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Press q to quit, DON'T close the window or the server process will just keep running"
While ($KeyInfo.VirtualKeyCode -eq $Null -or $KeyInfo.VirtualKeyCode -ne 81) {
$KeyInfo = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
}
}
finally {
Quit-CoreKeeperServer
pause
}

Solved problem like this:
# Feel free to change these (see README), but keep in mind that changes to this file might be overwritten on update
$CoreKeeperArguments = #("-batchmode", "-logfile", "CoreKeeperServerLog.txt") + $args
$script:ckpid = $null
function Quit-CoreKeeperServer {
if ($script:ckpid -ne $null) {
taskkill /pid $ckpid.Id
Wait-Process -InputObject $ckpid
Write-Host "Stopped CoreKeeperServer.exe"
}
}
try {
if (Test-Path -Path "GameID.txt") {
Remove-Item -Path "GameID.txt"
}
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
# Wait for GameID
while (!(Test-Path -Path "GameID.txt")) {
Start-Sleep -Milliseconds 100
}
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Server is runnig. Press CTRL + C to stop."
While ($true) {
if (-not (Get-Process -Id $ckpid.Id -ErrorAction SilentlyContinue)) {
Write-Host “Server is died restarting...”
Get-Date
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Server is runnig. Press CTRL + C to stop."
sleep 1
}
}
}
finally {
Quit-CoreKeeperServer
pause
}

Related

Powershell: Write to a single Logfile

I have written a small function that writes into a single logfile for each server this module is installed, but somehow a few cycles to write the line are lost, sometime I get the error file already in use. I have tested this function on different version of Powershell, PS5.1, PS6, PS7.1 x86 and x64 all with the same result.
if (!(Test-Path -ErrorAction Stop -Path (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log"))) {
New-Item -Path $ScriptLogPath -Name "$FQDN.log" -Force -ItemType File -ErrorAction Stop
}
$CreatedNew = $false
$Global:MTX = New-Object -TypeName System.Threading.Mutex($true, 'Global\LogFileMutex', [ref]$CreatedNew)
try {
if (-not $CreatedNew) {
$Global:MTX.WaitOne(10000) | Out-Null
}
}
catch [System.Threading.AbandonedMutexException] {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
($Date + $InstanceId + $Severity + $ScriptLineNumber + $ScriptName + $Message) | Out-File -Append -FilePath (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log")
}
catch {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
finally {
if ($Null -ne $Global:MTX) {
[void]$Global:MTX.ReleaseMutex()
[void]$Global:MTX.Dispose()
}
}
That is the block of code that will append the formatted string into the file.
So fare I have tried different approaches to handle the Mutex, made them globally and not. Used different approaches to append the string to the file, like Add-Content.
To try out the function I use this little script, it should write 150 lines, but end up with about 130 and no single error.
for ($num1 = 1 ; $num1 -le 5 ; $num1++) {
Start-Job {
for ($num = 1 ; $num -le 10 ; $num++) {
Write-Log -Severity 'INFO' -Message "Starting Job $num"
Start-Job -ScriptBlock {
try {
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "Starting"
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "DONE"
}
catch {
New-Item -Path "C:\Users\User\Desktop" -Name (Get-Date -Format "dd/MM/yyyy HH:mm:ss") -ItemType File
}
}
}
}
Since every server is writing to the same logfile, i dont think that you can override "error file already in use"
Iam thinking that maby you should think over the concept and run from a server where you are monitoring by invoke-commmand ?

how to automate Wusa with remoting and overcoming the lack of wait cmd

Scenario: Taking a list of kb files and executing them remotely with WUSA install of target machines across network.
The flow is like this:
enter a powershell session W/ target computer
for each loop $KB in $List
wusa.exe $kb
wait 'til installed
back to the wusa.exe for next device in $list
Code:
# SET UP ERROR-HANDLING FOR THE PS SESSION
$ErrorActionPreference = "continue"
# DECLARE VARIABLE THAT CONTAINS LIST OF PATCHES AVAILABLE FOR INSTALL
$PatchList = (Get-ChildItem -Path C:\WinPatch -recurse | Where-Object {$_.Extension -eq '.msu'})
# ESTABLISH LOOP TO ITERATE THRU PATCHES
foreach ($Patch in $PatchList)
{
Try
{
Write-Host ("`n Preparing to install: " + $Patch) -ForegroundColor Yellow
Write-Host ("`n Installing...") -ForegroundColor Magenta
$SB = {
$arglist = "$Patch", "/quiet", "/norestart"
Start-Process -FilePath "C:\windows\system32\wusa.exe" -ArgumentList $arglist -Wait}
Invoke-Command -ScriptBlock $SB
Write-Host "`n Installation complete`n" -ForegroundColor Green
}
Catch
{
[System.Exception]
Write-Host "Installation failed with Error -- $Error()" -ForegroundColor Red
$Error.Clear()
}
}
# RESTART OPTIONS
$Ans1 = Read-Host "`n Would you like to restart this computer now (Type Y for yes or N for no)"
if ($Ans1 -eq 'Y' -or $Ans1 -eq 'y')
{
Remove-Item -Path C:\WinPatch\*.msu -Force
Write-Host "`n This computer will restart in 5 seconds..." -ForegroundColor Yellow
Start-Sleep -Seconds 5
Restart-Computer -Force
}
else
{
# TEST COMPUTER FOR PS VERSION
$Tester = test-wsman -computername localhost | Select-Object -Property ProductVersion
if ($Tester.ProductVersion.EndsWith("1.0"))
{
Write-Host "`n This computer has PS v1.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
elseif ($Tester.ProductVersion.EndsWith("2.0"))
{
Write-Host "`n This computer has PS v2.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
else
{
# SCHEDULE RESTART
Import-Module PSScheduledJob
$RST = Read-Host -Prompt "`n Enter date/time to restart computer...format is --> mm/dd/yyyy hh:mmAM/PM"
$Ans2 = Read-Host -Prompt "`n You entered: $RST... if this is correct, enter Y for yes"
if ($Ans2 -eq 'Y' -or $Ans2 -eq 'y')
{
$Nomen = Read-Host -Prompt "`n Enter name for scheduled restart "
$Trig = New-JobTrigger -Once -At $RST
Register-ScheduledJob -Name $Nomen -Trigger $Trig -ScriptBlock { Restart-Computer -force } -RunAs32
}
else
{
Write-Host "`n Please restart the script and try again" -ForegroundColor Red
}
}
}
Break
PsExec.exe -u $domain\$username -p $password -h -s -accepteula \\$computer wusa.exe /quiet 'C:\Program Files\WindowsPowershell\Modules\windows10.0-kb5005112-x64.msu' /wait /forcerestart
This works for me. If you wrap this in an Invoke-Command block you can run this remotely. :-)
Thanks,

Using PowerShell to start batch file for secure wipe USB drive

I'm using PowerShell to start a bat file that wipes a USB drive that connected.
If I use the script without Start-Process it works fine, but I'm wanting to connect multiple drives and have it wipe them simultaneously.
The script:
Register-WmiEvent -Class Win32_VolumeChangeEvent -SourceIdentifier VolumeChange
Write-Host (Get-Date -Format s) " Beginning script..."
do {
$newEvent = Wait-Event -SourceIdentifier volumeChange
$eventType = $newEvent.SourceEventArgs.NewEvent.EventType
$eventTypeName = switch ($eventType) {
1 {"Configuration changed"}
2 {"Device arrival"}
3 {"Device removal"}
4 {"docking"}
}
Write-Host (Get-Date -Format s) " Event detected = " $eventTypeName
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) " Drive name = " $driveLetter
Write-Host (Get-Date -Format s) " Drive label = " $driveLabel
# Execute process if drive matches specified condition(s)
if ($driveLabel -eq 'BBIFREE_01' -or $drivelabel -eq 'HD10') {
Write-Host (Get-Date -Format s) " Starting task in 3 seconds..."
Start-Sleep -Seconds 3
Start-Process -FilePath D:\wipe.bat $driveLetter, $driveLabel
Copy-Item -Path D:\Utilities1 -Destination $driveLetter -Recurse
$driveEject = New-Object -ComObject Shell.Application
$driveEject.Namespace(17).ParseName($driveLetter).InvokeVerb("Eject")
}
}
Remove-Event -SourceIdentifier VolumeChange
} while (1 -eq 1) #Loop until next event
Unregister-Event -SourceIdentifier VolumeChange
The bat file contents:
set arg1=%1
set arg2=%2
format %args1% /FS:NTFS /p:1 /V:%args2% /x /y
EDIT
To clarify: the script is to run continously on a specific PC where it should start the bat file (as in wipe the disk securely) every time it detects a disk being connected.
If I use:
D:\wipe.bat -ArgumentList `"$driveLetter",`"$driveLabel"
then it starts the wiping on 1 disk, and on 1 disk only.
I need it to detect multiple disks, that's why I used Start-Process, seeing as I thought it would run on the background and keep watching for new events.
EDIT2
I changed the code to avoid using -ArgumentList, see above.
If I put the echo command in my batch file as requested:
set arg1=E:
set arg2=BBIFREE_01
ECHO ECHO IS ON
ECHO ECHO IS ON
So I see the commands in the bat file, but it doesn't execute and goes straight for the copy command.
This is a slightly modified version of a Script I wrote a while back, I don't have time right now to confirm it works 100% but it should at least point you in the right direction, it just threads the actual wiping so it can handle other jobs in the background, then uses a global popup to warn when one is done to prevent having to block while the job is finishing.
Should be able to handle any number of devices at once, it uses PowerShell's Format-Volume command instead, but you could put a call to the BAT file inside the job instead.
$USBWhiteList = #( #Add wildcard items here, if a USB matches one it will be wiped.
"USB0*"
"*WIPE"
)
Enum EventNames{ Changed = 1 ; Inserted = 2 ; Removed = 3 ; Docking = 4 } #Names for events
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange -ErrorAction SilentlyContinue #register the event
do{
Write-Host "Monitoring for Disk events..." -Fore Yellow
$Event = Wait-Event -SourceIdentifier volumeChange #wait for a disk event
$EventType = [EventNames]$Event.SourceEventArgs.NewEvent.EventType #get the type of the event
Write-Host "Drive $($EventType), Processing..." -Fore Yellow -NoNewline
$Volume = Get-Volume -DriveLetter $Event.SourceEventArgs.NewEvent.DriveName -ErrorAction SilentlyContinue #get the volume details
$IsMatch = ($USBWhiteList|? {$Volume.FileSystemLabel -like $_}).Count -gt 0 #does it match our whitelist?
if (($EventType -eq [EventNames]::Inserted) -and $IsMatch){ #if a disk was inserted which matches the whitelist...
Write-Host "Volume $($Volume.DriveLetter): '$($Volume.FileSystemLabel)', Found, Wiping!" -Fore Green
Start-Job -ScriptBlock { param ($Volume) #Perform the wipe inside a job
$Disk = Get-Partition -DriveLetter $Volume.DriveLetter | Get-Disk
Clear-Disk -Number $Disk.Number -RemoveData -Confirm:$false
New-Partition -DiskNumber $Disk.Number -UseMaximumSize -IsActive -DriveLetter $Volume.DriveLetter
Format-Volume -FileSystem NTFS -DriveLetter $Volume.DriveLetter -Confirm:$false
Add-Type -AssemblyName 'System.Windows.Forms' #warn (globally) when it is finished, don't need to run wait/recieve job.
[System.Windows.Forms.MessageBox]::Show("Finished Wiping Disk $($Volume.DriveLetter)","Please Remove Disk")
} -ArgumentList $Volume | Out-Null
} else {
Write-Host "Ignoring" -Fore Red
}
Remove-Event -SourceIdentifier volumeChange
} while (1) #this should be modified to quit after x disks or something, the below commands won't get exec'd - could also use a Try/Finally and Ctrl+C the script.
Get-Job | Remove-Job -Force
Unregister-Event -SourceIdentifier volumeChange

Powershell script to Check and keep program running

Making a short script to keep a program that crashes every now and then running,
Here is what I am trying and been editing
$date = Get-Date -Format G
Function Loop {
{ if(Get-Process -Name notepad -ErrorAction SilentlyContinue) -eq $null
write-host -ForegroundColor red "Server Is Not Running"
.\eldorado.exe -launcher -dedicated -headless -window -height 300 -width 300
echo "Guardian Started Headless Eldorito Server $($date)" | Add-Content .\dedicatedServer.log }
else {write-host -ForegroundColor green "Server Is Running"
sleep 10
Loop
}
Loop
}
What am I doing wrong? / New to scripting / programming
Writing a mash of code, or not copy and pasting properly?
$date = Get-Date -Format G # OK
Function Loop { # OK
# no, your function now starts with a scriptblock
# no, your if () {} pattern is broken.
{if(Get-Process -Name notepad -ErrorAction SilentlyContinue) -eq $null
# ok, but formatting makes it hard to read
write-host -ForegroundColor red "Server Is Not Running"
.\eldorado.exe -launcher -dedicated -headless -window -height 300 -width 300
echo "Guardian Started Headless Eldorito Server $($date)" | Add-Content .\dedicatedServer.log }
#ok
else {write-host -ForegroundColor green "Server Is Running"
sleep 10
# not ok, your loop is now a recursive function call which will consumer more resources forever until it crashes
Loop
}
# what's this doing?
Loop
}
It doesn't seem like you need a function at all, just a loop..
->
$date = Get-Date -Format G
while ($true)
{
if ((Get-Process -Name notepad -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host -ForegroundColor Red "Server Is Not Running"
.\eldorado.exe -launcher -dedicated -headless -window -height 300 -width 300
"Guardian Started Headless Eldorito Server $($date)" | Add-Content .\dedicatedServer.log
}
else
{
write-host -ForegroundColor Green "Server Is Running"
}
Start-Sleep -Seconds 10
}

Equivalent for tail -f output\worker*.log in Powershell

I need to output several logfiles - while they are written - to the shell.
In the unix version of my script this is achieved by tail -f output\worker*.log. Note the wildcard.
In Powershell I tried Get-Content -Path "output\worker*.log" -Wait, but this only prints the first logfile it can find to the shell.
For completion, here is my code where i call my program:
foreach ($worker in $workers)
{
Write-Host " Start $worker in background"
$block = {& $args[0] $args[1] $args[2] $args[3] $args[4] $args[5] $args[6] 2> $args[7] > $args[8]}
start-job -name $worker -scriptblock $block -argumentlist `
"$strPath\worker\bin\win32\php.exe", `
"-q", `
"-c", `
"$strPath\worker\conf\php_win32.ini", `
"$strPath\worker\bin\os-independant\logfilefilter\logfilefilter.php", `
"-f", `
"$strPath\worker\$worker\conf\logfilefilter-$worker.xml", `
"$strPath\output\$worker-error.log", `
"$strPath\output\$worker.log"
}
Get-Content -Path "output\worker*.log" -Wait
In my test case there are 8 workers and logfiles( output\worker01.log, output\worker02.log, output\worker03.log, output\worker04.log, output\worker05.log, output\worker06.log, output\worker07.log, output\worker08.log )
Is there a workaround to output all these logfiles? Or is it possible to duplicate the stdout stream from the background process to print it in the shell?
You can duplicate the the output streams by reading them directly from the output buffers of the child jobs.
Here's a demo script. Note that for the Verbose output, I'm removing output from the buffer as it's read, so that it doesn't get re-displayed on subsequent reads. This doesn't appear to have any affect on the Receive Job buffers for the job. If you do a Receive Job on it after the script completes, you'll still get the Verbose output all over again.
$sb = {
$VerbosePreference = 'Continue'
for ($i = 1; $i -le 100; $i++ )
{
start-sleep -Milliseconds 150
Write-Verbose "$(get-date)"
write-progress -activity "Search in Progress" -status "$i% Complete:" -percentcomplete $i;}
}
$job = start-job -ScriptBlock $sb
$verbose = ($job.ChildJobs[0].Verbose)
While ($job.State -ne 'Completed')
{
$job.ChildJobs |
foreach {
Start-Sleep -seconds 1
$Pct_Complete = $_.Progress | select -last 1 | select -ExpandProperty PercentComplete
Write-Host "`rBackground job $($_.ID) is $Pct_Complete percent completed." -ForegroundColor Cyan
While ($verbose.count){
Write-Host $verbose[0] -ForegroundColor Gray
$verbose.removeat(0)}
}
}
write-host "`nDone!"