powershellscripting

How do I fix the problem in my Powershell code so that the checks continue to run?


I'm writing a Powershell script to "manage" a Palworld server. I also added commands to stop and restart it and some checks like if the server crashes it restarts or if the ram consumption is too high it restarts. These functions are executed again and again in a global loop, at least they should, because they only run through once until the command check, and then the loop waits for an input until it checks again. But it should check again and again and the input should continue normally.

How do I fix the problem in my Powershell code so that the checks continue to run?

Here is the code, without some personal informations. Console outputs and messages are in German, but should not be relevant.

# SteamCMD Pfad
$SteamPfad = '...\steamcmd.exe'

# Palworld
$PalworldPfad = '...\PalServer.exe'
$PalworldLaunchOptions = "-publiclobby -log"

# ARRCON
$ARRCONHost = '...'
$ARRCONPort = '...'
$ARRCONPassword = '...'

# Discord
$DiscordWebhook = '...'

# PowerShell Variablen
$ramThreshold = 2 * 1024 * 1024 * 1024  # 2GB for testing, normally 16GB max

function Start-Server {
    Write-Host "PalShell v.1.0 by Gerrxt"
    Write-Host "-------------------"
    Write-Host "Server Startet..."
    Start-Process -FilePath $PalworldPfad -ArgumentList $PalworldLaunchOptions

    $DiscordPayload = @{
        content = "Der Palworld Server wurde gestartet!"
        username = "Palshell"
        avatar_url = "..."
    }

    $DiscordJsonPayload = $DiscordPayload | ConvertTo-Json
    Invoke-RestMethod -Uri $DiscordWebhook -Method Post -Body $DiscordJsonPayload -ContentType "application/json"
}

function Stop-Server {
    Write-Host "Server Stoppt..."
    C:\Users\Administrator\Desktop\PalShell\ARRCON.exe -H $ARRCONHost -P $ARRCONPort -p $ARRCONPassword "save"
    C:\Users\Administrator\Desktop\PalShell\ARRCON.exe -H $ARRCONHost -P $ARRCONPort -p $ARRCONPassword "shutdown 10 Der Server stoppt in 10 Sekunden!"

    $DiscordPayload = @{
        content = "Der Palworld Server wird herruntergefahren!"
        username = "Palshell"
        avatar_url = "..."
    }

    $DiscordJsonPayload = $DiscordPayload | ConvertTo-Json
    Invoke-RestMethod -Uri $DiscordWebhook -Method Post -Body $DiscordJsonPayload -ContentType "application/json"
}

function Update-Server {
    Write-Host "Nach Updates wird gesucht..."

    $updateOutput = Start-Process -FilePath $SteamPfad -ArgumentList "+force_install_dir C:\...\Palworld +login anonymous +app_update 2394010 validate +quit" -NoNewWindow -Wait -PassThru

    if ($updateOutput.ExitCode -eq 0) {
        Write-Host "Es gibt kein neues Update."
    } else {
        Write-Host "Ein Update wurde gefunden und installiert. Der Server startet."

        $DiscordPayload = @{
            content = "Der Palworld Server wurde aktualisiert!"
            username = "Palshell"
            avatar_url = "..."
        }
    
        $DiscordJsonPayload = $DiscordPayload | ConvertTo-Json
        Invoke-RestMethod -Uri $DiscordWebhook -Method Post -Body $DiscordJsonPayload -ContentType "application/json"
    }
}

function Check-ProcessRAMUsage {
    param (
        [string]$processName,
        [long]$ramThreshold
    )

    $process = Get-Process -Name $processName -ErrorAction SilentlyContinue

    Write-Host "DEBUG: Internal RAM Check..."

    if ($process) {
        $ramUsage = $process.WorkingSet64

        if ($ramUsage -gt $ramThreshold) {
            Write-Host "Warnung: Der Server verwendet mehr als 16GB RAM!"

            $DiscordPayload = @{
                content = "Warnung: Der Palworld Server verwendet mehr als 16GB RAM!"
                username = "Palshell"
                avatar_url = "..."
            }
            
            $DiscordJsonPayload = $DiscordPayload | ConvertTo-Json
            Invoke-RestMethod -Uri $DiscordWebhook -Method Post -Body $DiscordJsonPayload -ContentType "application/json"

            Write-Host "Der Server wird neugestartet..."
            Stop-Server
            Start-Sleep -Seconds 15
            Start-Server
        }
    } else {
        Write-Host "Process $processName not found."
    }
}

function Commands {
    param (
        [ref]$stopRequested
    )
    
    $user_input = Read-Host "Nutze einen der PalShell Befehle (Hilfe: help)"
    
    switch ($user_input) {
        "stop" {
            Stop-Server
            Write-Output "Der Server wird heruntergefahren. Die Powershell schliesst automatisch."
            Start-Sleep -Seconds 5
            $stopRequested.Value = $true
        }
        "help" {
            Write-Output ""
            Write-Output "Befehle:"
            Write-Output "help (Zeige die Befehle an.)"
            Write-Output "stop (Palworld Server stoppt.)"
            Write-Output "neustart (Der Server wird neugestartet.)"
            Write-Output ""
        }
        "neustart" {
            Write-Output "Der Server wird neugestartet..."
            Stop-Server
            Start-Sleep -Seconds 15
            Start-Server
        }
        default {
            Write-Output "Diesen Befehl gibt es nicht. Nutze help um die Befehle anzuzeigen."
        }
    }
}

#Update-Server #Excluded for testing, but its working
Start-Server

$stopRequested = $false
$processName = "PalServer-Win64-Shipping-Cmd"

while (-not $stopRequested) {
    Write-Host "DEBUG: Server Status Check..."
    $serverProcess = Get-Process -Name $processName -ErrorAction SilentlyContinue
    if (-not $serverProcess) {
        Write-Host "Server gecrasht. Starte Neu..."

        $DiscordPayload = @{
            content = "Der Palworld Server ist gecrashed."
            username = "Palshell"
            avatar_url = "..."
        }
    
        $DiscordJsonPayload = $DiscordPayload | ConvertTo-Json
        Invoke-RestMethod -Uri $DiscordWebhook -Method Post -Body $DiscordJsonPayload -ContentType "application/json"

        Start-Server
    }

    Write-Host "DEBUG: Server RAM Check..."
    Check-ProcessRAMUsage -processName $processName -ramThreshold $ramThreshold

    Write-Host "DEBUG: Server Command Check..."
    Commands -stopRequested ([ref]$stopRequested)

    Start-Sleep -Seconds 1
}

I have tried many things, even asked some AI's, but never got a good answer. I also have no idea how it should work differently.


Solution

  • I would suggest making your code more readable, changing a little bit of the structure, moving the waits inside the same function that gets user input, and change user input to reading a single key by using the static methods of [System.Console] instead of trying to read a full word from the user.

    For readability, we can create an enum:

    enum UserRequest {
       DoNotStop
       DoStop
    }
    

    And use a more descriptive function name that simply returns a value from the above enum instead of requiring a parameter (NOTE: There is a section towards the end that you may want to uncomment):

    function Read-UserInputAndProcessIt {
       if ([console]::KeyAvailable) {
          $keyInfo = [System.Console]::ReadKey($true)
          $user_input = $keyInfo.KeyChar
           
          switch ($user_input) {
             "s" {
                Write-Host "The server is shutting down. PowerShell will close automatically."
                Start-Sleep -Seconds 5
                return [UserRequest]::DoStop
             }
             "h" {
                Write-Host "Commands:"
                Write-Host "h (help) - Show the commands."
                Write-Host "s (stop) - Stops the Palworld server."
                Write-Host "n (restart) - The server will restart."
                Start-Sleep -Seconds 1
                return [UserRequest]::DoNotStop
             }
             "n" {
                Write-Host "The server is restarting..."
                Start-Sleep -Seconds 15
                return [UserRequest]::DoNotStop
             }
             default {
                Write-Host "Unknown command. Use 'h' for help."
                Start-Sleep -Seconds 1
                return [UserRequest]::DoNotStop
             }
          }
       }
       #  Optionally uncomment this section if you want every loop to wait 1 second.
    #   else {
    #      Start-Sleep -Seconds 1
    #   }
    
       return [UserRequest]::DoNotStop
    }
    

    And, since your code always runs once, change your while into a do until:

    do {
       Write-Host "DEBUG: Server Status Check..."
       # Do stuff
    } until ((Read-UserInputAndProcessIt) -eq [UserRequest]::DoStop)