SteamPS.psm1

#Region '.\Enum\Enum.ps1' 0
enum ServerType {
    Dedicated = 0x64    #d
    NonDedicated = 0x6C #l
    SourceTV = 0x70     #p
}
enum OSType {
    Linux = 108   #l
    Windows = 119 #w
    Mac = 109     #m
    MacOsX = 111  #o
}
enum VAC {
    Unsecured = 0
    Secured = 1
}
enum Visibility {
    Public = 0
    Private = 1
}
#EndRegion '.\Enum\Enum.ps1' 20
#Region '.\Private\API\Get-SteamAPIKey.ps1' 0
function Get-SteamAPIKey {
    <#
    .SYNOPSIS
    Grabs API key secure string from file and converts back to plaintext.
 
    .DESCRIPTION
    Grabs API key secure string from file and converts back to plaintext.
 
    .EXAMPLE
    Get-SteamAPIKey
 
    Returns the API key secure string in plain text.
 
    .NOTES
    Author: sysgoblin (https://github.com/sysgoblin) and Frederik Hjorslev Poulsen
    #>


    [CmdletBinding()]
    Param (
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $SteamPSKey = Test-Path -Path "$env:AppData\SteamPS\SteamPSKey.json"
        if (-not $SteamPSKey) {
            $Exception = [Exception]::new("Steam Web API configuration file not found in '$env:AppData\SteamPS\SteamPSKey.json'. Run Connect-SteamAPI to configure an API key.")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                'SteamAPIKeyNotFound',
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                $SteamPSKey # usually the object that triggered the error, if possible
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }
        # cmdlet terminates here, no need for an `else`, only possible way to be here is
        # if previous condition was false
        try {
            $Config = Get-Content "$env:AppData\SteamPS\SteamPSKey.json"
            $APIKeySecString = $Config | ConvertTo-SecureString
            [System.Net.NetworkCredential]::new('', $APIKeySecString).Password
        } catch {
            $Exception = [Exception]::new("Could not decrypt API key from configuration file not found in '$env:AppData\SteamPS\SteamPSKey.json'. Run Connect-SteamAPI to configure an API key.")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                'InvalidAPIKey',
                [System.Management.Automation.ErrorCategory]::ParserError,
                $APIKey # usually the object that triggered the error, if possible
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Private\API\Get-SteamAPIKey.ps1' 60
#Region '.\Private\Server\Add-EnvPath.ps1' 0
function Add-EnvPath {
    <#
    .LINK
    https://gist.github.com/mkropat/c1226e0cc2ca941b23a9
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [ValidateSet('Machine', 'User', 'Session')]
        [string]$Container = 'Session'
    )

    process {
        Write-Verbose -Message "Container is set to: $Container"
        $Path = $Path.Trim()

        $containerMapping = @{
            Machine = [EnvironmentVariableTarget]::Machine
            User    = [EnvironmentVariableTarget]::User
            Session = [EnvironmentVariableTarget]::Process
        }

        $containerType = $containerMapping[$Container]
        $persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType).
            Split([System.IO.Path]::PathSeparator).Trim() -ne ''

        if ($persistedPaths -notcontains $Path) {
            # previous step with `Trim()` + `-ne ''` already takes care of empty tokens,
            # no need to filter again here
            $persistedPaths = ($persistedPaths + $Path) -join [System.IO.Path]::PathSeparator
            [Environment]::SetEnvironmentVariable('Path', $persistedPaths, $containerType)

            Write-Verbose -Message "Adding $Path to environment path."
        } else {
            Write-Verbose -Message "$Path is already located in env:Path."
        }
    } # Process
} # Cmdlet
#EndRegion '.\Private\Server\Add-EnvPath.ps1' 42
#Region '.\Private\Server\Get-PacketString.ps1' 0
function Get-PacketString {
    <#
    .SYNOPSIS
    Get a string in a byte stream.
 
    .DESCRIPTION
    Get a string in a byte stream.
 
    .PARAMETER Stream
    Accepts BinaryReader.
 
    .EXAMPLE
    Get-PacketString -Stream $Stream
 
    Assumes that you already have a byte stream. See more detailed usage in
    Get-SteamServerInfo.
 
    .NOTES
    Author: Jordan Borean and Chris Dent
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.BinaryReader]$Stream
    )

    process {
        # Check if the stream has any bytes available
        if ($Stream.BaseStream.Length -gt 0) {
            # Find the end of the string, terminated with \0 and convert that byte range to a string.
            $stringBytes = while ($true) {
                $byte = $Stream.ReadByte()
                if ($byte -eq 0) {
                    break
                }
                $byte
            }
        }

        if ($stringBytes.Count -gt 0) {
            [System.Text.Encoding]::UTF8.GetString($stringBytes)
        }
    } # Process
} # Cmdlet
#EndRegion '.\Private\Server\Get-PacketString.ps1' 46
#Region '.\Private\Server\Get-SteamPath.ps1' 0
function Get-SteamPath {
    [CmdletBinding()]
    param (
    )

    process {
        $SteamCMDPath = $env:Path.Split([System.IO.Path]::PathSeparator) |
            Where-Object -FilterScript { $_ -like '*SteamCMD*' }

        if ($null -ne $SteamCMDPath) {
            [PSCustomObject]@{
                'Path'       = $SteamCMDPath
                'Executable' = "$SteamCMDPath\steamcmd.exe"
            }
        } else {
            Write-Verbose -Message 'SteamCMD where not found on the environment path.'
        }
    } # Process
} # Cmdlet
#EndRegion '.\Private\Server\Get-SteamPath.ps1' 20
#Region '.\Public\API\Connect-SteamAPI.ps1' 0
function Connect-SteamAPI {
    <#
    .SYNOPSIS
    Create or update the Steam Web API config file.
 
    .DESCRIPTION
    Create or update the Steam Web API config file which contains the API key
    used to authenticate to those Steam Web API's that require authentication.
 
    .EXAMPLE
    Connect-SteamAPI
    Prompts the user for a Steam Web API key and sets the specified input within
    the config file.
 
    .INPUTS
    None. You cannot pipe objects to Connect-SteamAPI.
 
    .OUTPUTS
    None. Nothing is returned when calling Connect-SteamAPI.
 
    .NOTES
    Author: sysgoblin (https://github.com/sysgoblin) and Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Connect-SteamAPI.html
    #>


    [CmdletBinding()]
    param (
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        if (-not (Test-Path -Path "$env:AppData\SteamPS\SteamPSKey.json")) {
            try {
                $TargetObject = New-Item -Path "$env:AppData\SteamPS\SteamPSKey.json" -Force
                Write-Verbose -Message "Created config file at $env:AppData\SteamPS\SteamPSKey.json"
            } catch {
                $Exception = [Exception]::new("Unable to create file $env:AppData\SteamPS\SteamPSKey.json")
                $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $Exception,
                    "CreateSteamAPIKeyFailed",
                    [System.Management.Automation.ErrorCategory]::WriteError,
                    $TargetObject # usually the object that triggered the error, if possible
                )
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
        }

        $APIKey = Read-Host -Prompt 'Enter your Steam Web API key' -AsSecureString
        $Key = ConvertFrom-SecureString -SecureString $APIKey
        $Key | Out-File "$($env:AppData)\SteamPS\SteamPSKey.json" -Force
        Write-Verbose -Message "Saved key as secure string to config file."
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Public\API\Connect-SteamAPI.ps1' 63
#Region '.\Public\API\Disconnect-SteamAPI.ps1' 0
function Disconnect-SteamAPI {
    <#
    .SYNOPSIS
    Disconnects from the Steam API by removing the stored API key.
 
    .DESCRIPTION
    The Disconnect-SteamAPI cmdlet removes the stored Steam API key from the system. This effectively disconnects the current session from the Steam API.
 
    .PARAMETER Force
    When the Force switch is used, the cmdlet will skip the confirmation prompt and directly remove the API key.
 
    .EXAMPLE
    Disconnect-SteamAPI -Force
 
    This command will remove the stored Steam API key without asking for confirmation.
 
    .INPUTS
    None. You cannot pipe objects to Disconnect-SteamAPI.
 
    .OUTPUTS
    None. Nothing is returned when calling Disconnect-SteamAPI.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Disconnect-SteamAPI.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false,
            HelpMessage = 'Skip the confirmation prompt.')][switch]$Force
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
        $SteamAPIKey = "$env:AppData\SteamPS\SteamPSKey.json"
    }

    process {
        if ($Force -or $PSCmdlet.ShouldContinue($SteamAPIKey, 'Do you want to continue removing the API key?')) {
            if (Test-Path -Path $SteamAPIKey) {
                Remove-Item -Path $SteamAPIKey -Force
                Write-Verbose -Message "$SteamAPIKey were deleted."
            } else {
                $Exception = [Exception]::new("Steam Web API configuration file not found in '$env:AppData\SteamPS\SteamPSKey.json'.")
                $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $Exception,
                    'SteamAPIKeyNotFound',
                    [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                    $SteamPSKey
                )
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
        }
    }

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
}
#EndRegion '.\Public\API\Disconnect-SteamAPI.ps1' 63
#Region '.\Public\API\Find-SteamAppID.ps1' 0
function Find-SteamAppID {
    <#
    .SYNOPSIS
    Find a Steam AppID by searching the name of the application.
 
    .DESCRIPTION
    Find a Steam AppID by searching the name of the application.
 
    .PARAMETER ApplicationName
    Enter the name of the application. If multiple hits the user will be presented
    with an Out-GridView where he/she can choose the correct application.
 
    .INPUTS
    System.String. Find-SteamAppID accepts a string value.
 
    .OUTPUTS
    System.String and Int. It returns the application name and application ID.
 
    .EXAMPLE
    Find-SteamAppID -ApplicationName 'Ground Branch'
 
    Will results in multiple hits and let the user choose between the application
    'Ground Branch' which is the game or 'Ground Branch Dedicated Server' which
    is the dedicated server to 'Ground Branch'.
 
    .EXAMPLE
    Find-SteamAppID -ApplicationName 'Ground Branch D'
 
    This Will only yield one result which is 'Ground Branch Dedicated Server'.
    Output is the AppID and name of the application.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Find-SteamAppID.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Position = 0,
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('GameName')]
        [string]$ApplicationName
    )

    begin {
        # Get most recent list with all Steam Apps ID and corresponding title and put it into a variable.
        Write-Verbose -Message "Getting all applications and the corresponding ID."
        $SteamApps = ((Invoke-WebRequest -Uri 'https://api.steampowered.com/ISteamApps/GetAppList/v2/' -UseBasicParsing).Content | ConvertFrom-Json).applist.apps
    }

    process {
        $SteamApps = $SteamApps | Where-Object -FilterScript { $PSItem.name -like "$ApplicationName*" }

        # If only one application is found when searching by application name.
        if (($SteamApps | Measure-Object).Count -eq 1) {
            Write-Verbose -Message "Only one application found: $($SteamApps.name) - $($SteamApps.appid)."
            Write-Output -InputObject $SteamApps
        }
        # If more than one application is found the user is prompted to select the exact application.
        elseif (($SteamApps | Measure-Object).Count -ge 1) {
            # An OutGridView is presented to the user where the exact AppID can be located. This variable contains the AppID selected in the Out-GridView.
            $SteamApp = $SteamApps | Select-Object @{Name='appid';Expression={$_.appid.toString() } },name | Out-GridView -Title 'Select application' -PassThru
            Write-Verbose -Message "$(($SteamApp).name) - $(($SteamApp).appid) selected from Out-GridView."
            Write-Output -InputObject $SteamApp
        }
    } # Process
}# Cmdlet
#EndRegion '.\Public\API\Find-SteamAppID.ps1' 72
#Region '.\Public\API\Get-SteamFriendList.ps1' 0
function Get-SteamFriendList {
    <#
    .SYNOPSIS
    Returns the friend list of any Steam user.
 
    .DESCRIPTION
    Retrieves the friend list of a Steam user whose profile visibility is set to "Public".
 
    .PARAMETER SteamID64
    Specifies the 64-bit Steam ID of the user whose friend list will be retrieved.
 
    .PARAMETER Relationship
    Specifies the relationship type to filter the friend list. Possible values are 'all' or 'friend'. Default is 'friend'.
 
    .PARAMETER OutputFormat
    Specifies the format of the output. Options are 'json' (default), 'xml', or 'vdf'.
 
    .EXAMPLE
    Get-SteamFriendList -SteamID64 76561197960435530
 
    Retrieves the friend list of the specified user.
 
    .EXAMPLE
    Get-SteamFriendList -SteamID64 76561197960435530 -OutputFormat xml
 
    Retrieves the friend list of the specified user and outputs it in XML format.
 
    .INPUTS
    System.Int64
 
    .OUTPUTS
    Returns a string formatted as JSON, XML, or VDF representing the user's friend list.
    The friend list contains the following properties:
    - steamid: 64-bit Steam ID of the friend.
    - relationship: Relationship qualifier.
    - friend_since: Unix timestamp of when the relationship was established.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Get-SteamFriendList.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Specifies the 64-bit Steam ID of the user whose friend list will be retrieved.',
            ValueFromPipelineByPropertyName = $true)]
        [int64]$SteamID64,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Specifies the relationship type to filter the friend list. Possible values are "all" or "friend". Default is "friend".')]
        [ValidateSet('all', 'friend')]
        [string]$Relationship = 'friend',

        [Parameter(Mandatory = $false,
            HelpMessage = 'Specifies the format of the output. Options are "json" (default), "xml", or "vdf".')]
        [ValidateSet('json', 'xml', 'vdf')]
        [string]$OutputFormat = 'json'
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $Request = Invoke-WebRequest -Uri "https://api.steampowered.com/ISteamUser/GetFriendList/v1/?key=$(Get-SteamAPIKey)&steamid=$SteamID64&relationship=$Relationship&format=$OutputFormat" -UseBasicParsing

        Write-Output -InputObject $Request.Content
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Public\API\Get-SteamFriendList.ps1' 77
#Region '.\Public\API\Get-SteamNews.ps1' 0
function Get-SteamNews {
    <#
    .SYNOPSIS
    Returns the latest news of a game specified by its AppID.
 
    .DESCRIPTION
    Returns the latest news of a game specified by its AppID.
 
    .PARAMETER AppID
    AppID of the game you want the news of.
 
    .PARAMETER Count
    How many news entries you want to get returned.
 
    .PARAMETER MaxLength
    Maximum length of each news entry.
 
    .PARAMETER OutputFormat
    Format of the output. Options are json (default), xml or vdf.
 
    .EXAMPLE
    Get-SteamNews -AppID 440
 
    Lists number of news that are available for the AppID.
 
    .EXAMPLE
    Get-SteamNews -AppID 440 -Count 1
 
    Retrieves 1 (the latest) news item for the AppID 440.
 
    .INPUTS
    int64
 
    .OUTPUTS
    Returns a string that is either formatted as json, xml or vdf.
 
    An appnews object containing:
 
    appid, the AppID of the game you want news of
 
    newsitems, an array of news item information:
    - An ID, title and url.
    - A shortened excerpt of the contents (to maxlength characters), terminated by "..." if longer than maxlength.
    - A comma-separated string of labels and UNIX timestamp.
 
    .NOTES
    Author: Frederik Hjorslelv Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Get-SteamNews.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'AppID of the game you want the news of.')]
        [int]$AppID,

        [Parameter(Mandatory = $false,
            HelpMessage = 'How many news entries you want to get returned.')]
        [int]$Count,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Maximum length of each news entry.')]
        [int]$MaxLength,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Format of the output. Options are json (default), xml or vdf.')]
        [ValidateSet('json', 'xml', 'vdf')]
        [string]$OutputFormat = 'json'
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $Request = Invoke-WebRequest -Uri "http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=$AppID&count=$Count&maxlength=$MaxLength&format=$OutputFormat" -UseBasicParsing

        Write-Output -InputObject $Request.Content
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Public\API\Get-SteamNews.ps1' 87
#Region '.\Public\API\Get-SteamPlayerBan.ps1' 0
function Get-SteamPlayerBan {
    <#
    .SYNOPSIS
    Returns Community, VAC, and Economy ban statuses for given players.
 
    .DESCRIPTION
    Returns Community, VAC, and Economy ban statuses for given players.
 
    .PARAMETER SteamID64
    Comma-delimited list of 64 bit Steam IDs to return player ban information for.
 
    .PARAMETER OutputFormat
    Format of the output. Options are json (default), xml or vdf.
 
    .EXAMPLE
    Get-SteamPlayerBan -SteamID64 76561197960435530, 76561197960434622
 
    .INPUTS
    Array of int64.
 
    .OUTPUTS
    Returns a string that is either formatted as json, xml or vdf.
 
    players: List of player ban objects for each 64 bit ID requested
    - SteamId (string) The player's 64 bit ID.
    - CommunityBanned (bool) Indicates whether or not the player is banned from Steam Community.
    - VACBanned (bool) Indicates whether or not the player has VAC bans on record.
    - NumberOfVACBans (int) Number of VAC bans on record.
    - DaysSinceLastBan (int) Number of days since the last ban.
    - NumberOfGameBans (int) Number of bans in games, this includes CS:GO Overwatch bans.
    - EconomyBan (string) The player's ban status in the economy. If the player has no bans on record the string will be "none", if the player is on probation it will say "probation", etc.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Get-SteamPlayerBan.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = '64 bit Steam ID to return player bans for.',
            ValueFromPipelineByPropertyName = $true)]
        [int64[]]$SteamID64,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Format of the output. Options are json (default), xml or vdf.')]
        [ValidateSet('json', 'xml', 'vdf')]
        [string]$OutputFormat = 'json'
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $Request = Invoke-WebRequest -Uri "https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?format=$OutputFormat&key=$(Get-SteamAPIKey)&steamids=$($SteamID64 -join ',')" -UseBasicParsing

        Write-Output -InputObject $Request.Content
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Public\API\Get-SteamPlayerBan.ps1' 67
#Region '.\Public\API\Get-SteamPlayerSummary.ps1' 0
function Get-SteamPlayerSummary {
    <#
    .SYNOPSIS
    Returns basic profile information for a list of 64-bit Steam IDs.
 
    .DESCRIPTION
    Returns basic profile information for a list of 64-bit Steam IDs.
 
    .PARAMETER SteamID64
    Comma-delimited list of 64 bit Steam IDs to return profile information for.
    Up to 100 Steam IDs can be requested.
 
    .PARAMETER OutputFormat
    Format of the output. Options are json (default), xml or vdf.
 
    .EXAMPLE
    Get-SteamPlayerSummary -SteamID64 76561197960435530, 76561197960434622
 
    .INPUTS
    Array of int64.
 
    .OUTPUTS
    Returns a string that is either formatted as json, xml or vdf.
 
    Some data associated with a Steam account may be hidden if the user has their
    profile visibility set to "Friends Only" or "Private". In that case, only
    public data will be returned.
 
    Public Data
    - steamid: 64bit SteamID of the user
    - personaname: The player's persona name (display name)
    - profileurl: The full URL of the player's Steam Community profile.
    - avatar: The full URL of the player's 32x32px avatar. If the user hasn't configured an avatar, this will be the default ? avatar.
    - avatarmedium: The full URL of the player's 64x64px avatar. If the user hasn't configured an avatar, this will be the default ? avatar.
    - avatarfull: The full URL of the player's 184x184px avatar. If the user hasn't configured an avatar, this will be the default ? avatar.
    - personastate: The user's current status. 0 - Offline, 1 - Online, 2 - Busy, 3 - Away, 4 - Snooze, 5 - looking to trade, 6 - looking to play. If the player's profile is private, this will always be "0", except if the user has set their status to looking to trade or looking to play, because a bug makes those status appear even if the profile is private.
    - communityvisibilitystate: This represents whether the profile is visible or not, and if it is visible, why you are allowed to see it. Note that because this WebAPI does not use authentication, there are only two possible values returned: 1 - the profile is not visible to you (Private, Friends Only, etc), 3 - the profile is "Public", and the data is visible. Mike Blaszczak's post on Steam forums says, "The community visibility state this API returns is different than the privacy state. It's the effective visibility state from the account making the request to the account being viewed given the requesting account's relationship to the viewed account."
    - profilestate: If set, indicates the user has a community profile configured (will be set to '1')
    - lastlogoff: The last time the user was online, in unix time. Only available when you are friends with the requested user (since Feb, 4).
    - commentpermission: If set, indicates the profile allows public comments.
 
    Private Data
    - realname: The player's "Real Name", if they have set it.
    - primaryclanid: The player's primary group, as configured in their Steam Community profile.
    - timecreated: The time the player's account was created.
    - gameid: If the user is currently in-game, this value will be returned and set to the gameid of that game.
    - gameserverip: The ip and port of the game server the user is currently playing on, if they are playing on-line in a game using Steam matchmaking. Otherwise will be set to "0.0.0.0:0".
    - gameextrainfo: If the user is currently in-game, this will be the name of the game they are playing. This may be the name of a non-Steam game shortcut.
    - cityid: This value will be removed in a future update (see loccityid)
    - loccountrycode: If set on the user's Steam Community profile, The user's country of residence, 2-character ISO country code
    - locstatecode: If set on the user's Steam Community profile, The user's state of residence
    - loccityid: An internal code indicating the user's city of residence. A future update will provide this data in a more useful way. steam_location gem/package makes player location data readable for output.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Get-SteamPlayerSummary.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = '64 bit Steam ID to return player summary for.',
            ValueFromPipelineByPropertyName = $true)]
        [int64[]]$SteamID64,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Format of the output. Options are json (default), xml or vdf.')]
        [ValidateSet('json', 'xml', 'vdf')]
        [string]$OutputFormat = 'json'
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $Request = Invoke-WebRequest -Uri "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?format=$OutputFormat&key=$(Get-SteamAPIKey)&steamids=$($SteamID64 -join ',')" -UseBasicParsing

        Write-Output -InputObject $Request.Content
    }

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
}
#EndRegion '.\Public\API\Get-SteamPlayerSummary.ps1' 88
#Region '.\Public\API\Resolve-VanityURL.ps1' 0
function Resolve-VanityURL {
    <#
    .SYNOPSIS
    Resolve a vanity URL (also named custom URL).
 
    .DESCRIPTION
    Resolve a vanity URL (also named custom URL) and return the 64 bit SteamID
    that belongs to said URL.
 
    .PARAMETER VanityURL
    Enter the vanity URL (also named custom URL) to get a SteamID for. Do not enter
    the fully qualified URL, but just the ID e.g. hjorslev instead of
    "https://steamcommunity.com/id/hjorslev/"
 
    .PARAMETER UrlType
    The type of vanity URL. 1 (default): Individual profile, 2: Group, 3: Official game group
 
    .PARAMETER OutputFormat
    Format of the output. Options are json (default), xml or vdf.
 
    .EXAMPLE
    Resolve-VanityURL -VanityURL hjorslev
 
    Returns a 64 bit Steam ID.
 
    .INPUTS
    String.
 
    .OUTPUTS
    64 bit Steam ID.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Resolve-VanityURL.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Enter the vanity URL (also named custom URL) to get a SteamID for.')]
        [ValidateScript( {
                if (([System.URI]$_ ).IsAbsoluteUri -eq $true) {
                    throw "Do not enter the fully qualified URL, but just the ID (e.g.) everything after https://steamcommunity.com/id/"
                }
                $true
            })]
        [string]$VanityURL,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The type of vanity URL. 1 (default): Individual profile, 2: Group, 3: Official game group.')]
        [ValidateSet(1, 2, 3)]
        [int]$UrlType = 1,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Format of the output. Options are json (default), xml or vdf.')]
        [ValidateSet('json', 'xml', 'vdf')]
        [string]$OutputFormat = 'json'
    )

    begin {
        Write-Verbose -Message "[BEGIN ] Starting: $($MyInvocation.MyCommand)"
    }

    process {
        $Request = Invoke-WebRequest -Uri "https://api.steampowered.com/ISteamUser/ResolveVanityURL/v1/?key=$(Get-SteamAPIKey)&vanityurl=$VanityURL&url_type=$UrlType&format=$OutputFormat" -UseBasicParsing

        if (($Request.Content | ConvertFrom-Json).response.success -eq '1') {
            [PSCustomObject]@{
                'SteamID64' = ([int64]($Request.Content | ConvertFrom-Json).response.steamid)
            }
        } elseif (($Request.Content | ConvertFrom-Json).response.success -eq '42') {
            $Exception = [Exception]::new("Unable to find $VanityURL.")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                "VanityURLNotFound",
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                ($Request.Content | ConvertFrom-Json).response.success
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }
    } # Process

    end {
        Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)"
    }
} # Cmdlet
#EndRegion '.\Public\API\Resolve-VanityURL.ps1' 89
#Region '.\Public\Server\Get-SteamServerInfo.ps1' 0
function Get-SteamServerInfo {
    <#
    .SYNOPSIS
    Query a running steam based game server.
 
    .DESCRIPTION
    The cmdlet fetches server information from a running game server using UDP/IP packets.
    It will return information ServerName, Map, InstallDir, GameName, AppID, Players
    MaxPlayers, Bots, ServerType, Environment, Visibility, VAC andVersion.
 
    .PARAMETER IPAddress
    Enter the IP address of the Steam based server.
 
    .PARAMETER Port
    Enter the port number of the Steam based server.
 
    .PARAMETER Timeout
    Timeout in milliseconds before giving up querying the server.
 
    .EXAMPLE
    Get-SteamServerInfo -IPAddress '185.15.73.207' -Port 27015
 
    ```
    Protocol : 17
    ServerName : SAS Proving Ground 10 (EU)
    Map : TH-SmallTown
    InstallDir : groundbranch
    GameName : Ground Branch
    AppID : 16900
    Players : 6
    MaxPlayers : 10
    Bots : 0
    ServerType : Dedicated
    Environment : Windows
    Visibility : Public
    VAC : Unsecured
    Version : 1.0.0.0
    ExtraDataFlag : 177
    IPAddress : 185.15.73.207
    Port : 27015
    ```
 
    .NOTES
    Author: Jordan Borean, Chris Dent and Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Get-SteamServerInfo.html
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Enter the IP address of the Steam based server.')]
        [System.Net.IPAddress]$IPAddress,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Enter the port number of the Steam based server.')]
        [int]$Port,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Timeout in milliseconds before giving up querying the server.')]
        [int]$Timeout = 5000
    )

    begin {
        # A2S_INFO: Retrieves information about the server including, but not limited to: its name, the map currently being played, and the number of players.
        # https://developer.valvesoftware.com/wiki/Server_queries#A2S_INFO
        $A2S_INFO = [byte]0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00
    }

    process {
        try {
            # Instantiate client and endpoint
            $Client = New-Object -TypeName Net.Sockets.UDPClient(0)
            [void]$Client.Send($A2S_INFO, $A2S_INFO.Length, $IPAddress, $Port)
            $Client.Client.SendTimeout = $Timeout
            $Client.Client.ReceiveTimeout = $Timeout
            $IPEndpoint = New-Object -TypeName Net.IPEndpoint([Net.IPAddress]::Any, 0)

            # The first 4 bytes are 255 which seems to be some sort of header.
            $ReceivedData = $Client.Receive([Ref]$IPEndpoint) | Select-Object -Skip 4
            $Stream = [System.IO.BinaryReader][System.IO.MemoryStream][Byte[]]$ReceivedData

            # Challenge:
            if ($Stream.ReadByte() -eq 65) {
                # If the response is a challenge, resend query with last 4 bytes of the challenge
                $challenge = while ($Stream.BaseStream.Position -lt $Stream.BaseStream.Length) {
                    $Stream.ReadByte()
                }
                $newQuery = $A2S_INFO + $challenge

                [void]$Client.Send($newQuery, $newQuery.Length, $IPAddress, $Port)
                # The first 4 bytes are 255 which seems to be some sort of header.
                $ReceivedData = $Client.Receive([Ref]$IPEndpoint) | Select-Object -Skip 4
                $Stream = [System.IO.BinaryReader][System.IO.MemoryStream][Byte[]]$ReceivedData
            } else {
                $Stream.BaseStream.Position = 0
            }

            $Client.Close()
        } catch {
            $Exception = [Exception]::new("Could not reach server {0}:{1}.") -f $IPAddress, $Port
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                "ServerNotFound",
                [System.Management.Automation.ErrorCategory]::ConnectionError,
                $ReceivedData
            )
            $PSCmdlet.WriteError($ErrorRecord)
        }

        # If we cannot reach the server we will not display the empty object.
        if ($Stream) {
            # This is also a header - that will always be equal to 'I' (0x49).
            $Stream.ReadByte() | Out-Null
            [PSCustomObject]@{
                Protocol      = [int]$Stream.ReadByte()
                ServerName    = Get-PacketString -Stream $Stream
                Map           = Get-PacketString -Stream $Stream
                InstallDir    = Get-PacketString -Stream $Stream
                GameName      = Get-PacketString -Stream $Stream
                AppID         = [int]$Stream.ReadUInt16()
                Players       = [int]$Stream.ReadByte()
                MaxPlayers    = [int]$Stream.ReadByte()
                Bots          = $Stream.ReadByte()
                ServerType    = [ServerType]$Stream.ReadByte()
                Environment   = [OSType]$Stream.ReadByte()
                Visibility    = [Visibility]$Stream.ReadByte()
                VAC           = [VAC]$Stream.ReadByte()
                Version       = Get-PacketString -Stream $Stream
                ExtraDataFlag = $Stream.ReadByte()
                IPAddress     = $IPAddress
                Port          = $Port
            } # PSCustomObject
        }
    } # Process
} # Cmdlet
#EndRegion '.\Public\Server\Get-SteamServerInfo.ps1' 138
#Region '.\Public\Server\Install-SteamCMD.ps1' 0
function Install-SteamCMD {
    <#
    .SYNOPSIS
    Install SteamCMD.
 
    .DESCRIPTION
    This cmdlet downloads SteamCMD and configures it in a custom or
    predefined location (C:\Program Files\SteamCMD).
 
    .PARAMETER InstallPath
    Specifiy the install location of SteamCMD.
 
    .PARAMETER Force
    The Force parameter allows the user to skip the "Should Continue" box.
 
    .EXAMPLE
    Install-SteamCMD
 
    Installs SteamCMD in C:\Program Files\SteamCMD.
 
    .EXAMPLE
    Install-SteamCMD -InstallPath 'C:'
 
    Installs SteamCMD in C:\SteamCMD.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Install-SteamCMD.html
    #>


    [CmdletBinding(SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateScript( {
                if ($_.Substring(($_.Length -1)) -eq '\') {
                    throw "InstallPath may not end with a trailing slash."
                }
                $true
            })]
        [string]$InstallPath = "$env:ProgramFiles",

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )

    begin {
        $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
        if ($isAdmin -eq $false) {
            $Exception = [Exception]::new('The current PowerShell session is not running as Administrator. Start PowerShell by using the Run as Administrator option, and then try running the script again.')
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                'MissingUserPermissions',
                [System.Management.Automation.ErrorCategory]::PermissionDenied,
                $isAdmin
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }
    }

    process {
        if ($Force -or $PSCmdlet.ShouldContinue('Would you like to continue?', 'Install SteamCMD')) {
            # Ensures that SteamCMD is installed in a folder named SteamCMD.
            $InstallPath = $InstallPath + '\SteamCMD'

            if (-not ((Get-SteamPath).Path -eq $InstallPath)) {
                Write-Verbose -Message "Adding $InstallPath to Environment Variable PATH."
                Add-EnvPath -Path $InstallPath -Container Machine
            } else {
                Write-Verbose -Message "Path $((Get-SteamPath).Path) already exists."
            }

            $TempDirectory = 'C:\Temp'
            if (-not (Test-Path -Path $TempDirectory)) {
                Write-Verbose -Message 'Creating Temp directory.'
                New-Item -Path 'C:\' -Name 'Temp' -ItemType Directory | Write-Verbose
            }

            # Download SteamCMD.
            Invoke-WebRequest -Uri 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip' -OutFile "$TempDirectory\steamcmd.zip" -UseBasicParsing

            # Create SteamCMD directory if necessary.
            if (-not (Test-Path -Path $InstallPath)) {
                Write-Verbose -Message "Creating SteamCMD directory: $InstallPath"
                New-Item -Path $InstallPath -ItemType Directory | Write-Verbose
                Expand-Archive -Path "$TempDirectory\steamcmd.zip" -DestinationPath $InstallPath
            }

            # Doing some initial configuration of SteamCMD. The first time SteamCMD is launched it will need to do some updates.
            Write-Host -Object 'Configuring SteamCMD for the first time. This might take a little while.'
            Write-Host -Object 'Please wait' -NoNewline
            Start-Process -FilePath "$InstallPath\steamcmd.exe" -ArgumentList 'validate +quit' -WindowStyle Hidden
            do {
                Write-Host -Object "." -NoNewline
                Start-Sleep -Seconds 3
            }
            until (-not (Get-Process -Name "*steamcmd*"))
        }
    } # Process

    end {
        if (Test-Path -Path "$TempDirectory\steamcmd.zip") {
            Remove-Item -Path "$TempDirectory\steamcmd.zip" -Force
        }

        if (Test-Path -Path (Get-SteamPath).Executable) {
            Write-Output -InputObject "SteamCMD is now installed. Please close/open your PowerShell host."
        }
    } # End
} # Cmdlet
#EndRegion '.\Public\Server\Install-SteamCMD.ps1' 113
#Region '.\Public\Server\Update-SteamApp.ps1' 0
function Update-SteamApp {
    <#
    .SYNOPSIS
    Install or update a Steam application using SteamCMD.
 
    .DESCRIPTION
    Install or update a Steam application using SteamCMD. If SteamCMD is missing, it will be installed first.
    You can either search for the application by name or enter the specific Application ID.
 
    .PARAMETER ApplicationName
    Enter the name of the app to make a wildcard search for the application.
 
    .PARAMETER AppID
    Enter the application ID you wish to install.
 
    .PARAMETER Credential
    If the app requires login to install or update, enter your Steam username and password.
 
    .PARAMETER Path
    Path to installation folder.
 
    .PARAMETER Arguments
    Enter any additional arguments here.
 
    Beware, the following arguments are already used:
 
    If you use Steam login to install/upload the app the following arguments are already used: "+force_install_dir $Path +login $SteamUserName $SteamPassword +app_update $SteamAppID $Arguments +quit"
 
    If you use anonymous login to install/upload the app the following arguments are already used: "+force_install_dir $Path +login anonymous +app_update $SteamAppID $Arguments +quit"
 
    .PARAMETER Force
    The Force parameter allows the user to skip the "Should Continue" box.
 
    .EXAMPLE
    Update-SteamApp -ApplicationName 'Arma 3' -Credential 'Toby' -Path 'C:\DedicatedServers\Arma3'
 
    Because there are multiple hits when searching for Arma 3, the user will be promoted to select the right application.
 
    .EXAMPLE
    Update-SteamApp -AppID 376030 -Path 'C:\DedicatedServers\ARK-SurvivalEvolved'
 
    Here we use anonymous login because the particular application (ARK: Survival Evolved Dedicated Server) doesn't require login.
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    SteamCMD CLI parameters: https://developer.valvesoftware.com/wiki/Command_Line_Options#Command-line_parameters_4
 
    .LINK
    https://hjorslev.github.io/SteamPS/Update-SteamApp.html
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification='Is implemented but not accepted by PSSA.')]
    [CmdletBinding(SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    param (
        [Parameter(Position = 0,
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ApplicationName'
        )]
        [Alias('GameName')]
        [string]$ApplicationName,

        [Parameter(Position = 0,
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'AppID'
        )]
        [int]$AppID,

        [Parameter(Mandatory = $true)]
        [ValidateScript( {
                if ($_.Substring(($_.Length -1)) -eq '\') {
                    throw "Path may not end with a trailing slash."
                }
                $true
            })]
        [string]$Path,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,

        [Parameter(Mandatory = $false)]
        [string]$Arguments,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )

    begin {
        if ($null -eq (Get-SteamPath)) {
            throw 'SteamCMD could not be found in the env:Path. Have you executed Install-SteamCMD?'
        }

        # Install SteamCMD if it is missing.
        if (-not (Test-Path -Path (Get-SteamPath).Executable)) {
            Start-Process powershell -ArgumentList '-NoExit -Command "Install-SteamCMD; exit"' -Verb RunAs
            Write-Verbose -Message 'Installing SteamCMD in another window. Please wait and try again.'
            throw "SteamCMD is missing and is being installed in another window. Please wait until the other window closes, restart your console, and try again."
        }
    } # Begin

    process {
        function Use-SteamCMD ($SteamAppID) {
            # If Steam username and Steam password are not empty we use them for logging in.
            if ($null -ne $Credential.UserName) {
                Write-Verbose -Message "Logging into Steam as $($Credential | Select-Object -ExpandProperty UserName)."
                $SteamCMDProcess = Start-Process -FilePath (Get-SteamPath).Executable -NoNewWindow -ArgumentList "+force_install_dir `"$Path`" +login $($Credential.UserName) $($Credential.GetNetworkCredential().Password) +app_update $SteamAppID $Arguments +quit" -Wait -PassThru
                if ($SteamCMDProcess.ExitCode -ne 0) {
                    Write-Error -Message ("SteamCMD closed with ExitCode {0}" -f $SteamCMDProcess.ExitCode) -Category CloseError
                }
            }
            # If Steam username and Steam password are empty we use anonymous login.
            elseif ($null -eq $Credential.UserName) {
                Write-Verbose -Message 'Using SteamCMD as anonymous.'
                $SteamCMDProcess = Start-Process -FilePath (Get-SteamPath).Executable -NoNewWindow -ArgumentList "+force_install_dir `"$Path`" +login anonymous +app_update $SteamAppID $Arguments +quit" -Wait -PassThru
                if ($SteamCMDProcess.ExitCode -ne 0) {
                    Write-Error -Message ("SteamCMD closed with ExitCode {0}" -f $SteamCMDProcess.ExitCode) -Category CloseError
                }
            }
        }

        # If game is found by searching for game name.
        if ($PSCmdlet.ParameterSetName -eq 'ApplicationName') {
            try {
                $SteamApp = Find-SteamAppID -ApplicationName $ApplicationName
                # Install selected Steam application if a SteamAppID has been selected.
                if (-not ($null -eq $SteamApp)) {
                    if ($Force -or $PSCmdlet.ShouldContinue("Do you want to install or update $(($SteamApp).name)?", "Update SteamApp $(($SteamApp).name)?")) {
                        Write-Verbose -Message "The application $(($SteamApp).name) is being updated. Please wait for SteamCMD to finish."
                        Use-SteamCMD -SteamAppID ($SteamApp).appid
                    } # Should Continue
                }
            } catch {
                Throw "$ApplicationName couldn't be updated."
            }
        } # ParameterSet ApplicationName

        # If game is found by using a unique AppID.
        if ($PSCmdlet.ParameterSetName -eq 'AppID') {
            try {
                $SteamAppID = $AppID
                # Install selected Steam application.
                if ($Force -or $PSCmdlet.ShouldContinue("Do you want to install or update $($SteamAppID)?", "Update SteamApp $($SteamAppID)?")) {
                    Write-Verbose -Message "The application with AppID $SteamAppID is being updated. Please wait for SteamCMD to finish."
                    Use-SteamCMD -SteamAppID $SteamAppID
                } # Should Continue
            } catch {
                Throw "$SteamAppID couldn't be updated."
            }
        } # ParameterSet AppID
    } # Process
} # Cmdlet
#EndRegion '.\Public\Server\Update-SteamApp.ps1' 159
#Region '.\Public\Server\Update-SteamServer.ps1' 0
function Update-SteamServer {
    <#
    .SYNOPSIS
    Update a Steam based game server.
 
    .DESCRIPTION
    This cmdlet presents a workflow to keep a steam based game server up to date.
    The server is expecting the game server to be running as a Windows Service.
 
    .PARAMETER AppID
    Enter the application ID you wish to install.
 
    .PARAMETER ServiceName
    Specify the Windows Service Name. You can get a list of services with Get-Service.
 
    .PARAMETER IPAddress
    Enter the IP address of the Steam based server.
 
    .PARAMETER Port
    Enter the port number of the Steam based server.
 
    .PARAMETER Path
    Install location of the game server.
 
    .PARAMETER Credential
    If the app requires login to install or update, enter your Steam username and password.
 
    .PARAMETER Arguments
    Enter any additional arguments here.
 
    .PARAMETER LogPath
    Specify the directory of the log files.
 
    .PARAMETER DiscordWebhookUri
    Enter a Discord Webhook Uri if you wish to get notifications about the server
    update.
 
    .PARAMETER AlwaysNotify
    Always receive a notification when a server has been updated. Default is
    only to send on errors.
 
    .PARAMETER TimeoutLimit
    Number of times the cmdlet checks if the server is online or offline. When
    the limit is reached an error is thrown.
 
    .EXAMPLE
    Update-SteamServer -AppID 476400 -ServiceName GB-PG10 -IPAddress '185.15.73.207' -Port 27015
 
    .NOTES
    Author: Frederik Hjorslev Nylander
 
    .LINK
    https://hjorslev.github.io/SteamPS/Update-SteamServer.html
    #>


    # TODO: Implement support for ShouldContinue. Due to compatibility we wait with this.
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
    [CmdletBinding(SupportsShouldProcess = $true,
        ConfirmImpact = 'High')]

    param (
        [Parameter(Mandatory = $true)]
        [int]$AppID,

        [Parameter(Mandatory = $true)]
        [ValidateScript( { Get-Service -Name $_ })]
        [string]$ServiceName,

        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [System.Net.IPAddress]$IPAddress,

        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [int]$Port,

        [Parameter(Mandatory = $false)]
        [Alias('ApplicationPath')]
        [string]$Path = "C:\DedicatedServers\$ServiceName",

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,

        [Parameter(Mandatory = $false)]
        [string]$Arguments,

        [Parameter(Mandatory = $false)]
        [Alias('LogLocation')]
        [string]$LogPath = "C:\DedicatedServers\Logs",

        [Parameter(Mandatory = $false)]
        [string]$DiscordWebhookUri,

        [Parameter(Mandatory = $false)]
        [string]$AlwaysNotify,

        [Parameter(Mandatory = $false)]
        [int]$TimeoutLimit = 10
    )

    begin {
        if ($null -eq (Get-SteamPath)) {
            throw 'SteamCMD could not be found in the env:Path. Have you executed Install-SteamCMD?'
        }

        # Log settings
        $PSFLoggingProvider = @{
            Name          = 'logfile'
            InstanceName  = '<taskname>'
            FilePath      = "$LogPath\$ServiceName\$ServiceName-%Date%.csv"
            Enabled       = $true
            LogRotatePath = "$LogPath\$ServiceName\$ServiceName-*.csv"
        }
        Set-PSFLoggingProvider @PSFLoggingProvider

        # Variable that stores how many times the cmdlet has checked whether the
        # server is offline or online.
        $TimeoutCounter = 0
    }

    process {
        # Get server status and output it.
        $ServerStatus = Get-SteamServerInfo -IPAddress $IPAddress -Port $Port -ErrorAction SilentlyContinue

        # If server is alive we check it is empty before updating it.
        if ($ServerStatus) {
            Write-PSFMessage -Level Host -Message $ServerStatus -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"

            # Waiting to server is empty. Checking every 60 seconds.
            while ($ServerStatus.Players -ne 0) {
                Write-PSFMessage -Level Host -Message 'Awaiting that the server is empty before updating.' -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
                $ServerStatus = Get-SteamServerInfo -IPAddress $IPAddress -Port $Port -ErrorAction SilentlyContinue
                Write-PSFMessage -Level Host -Message $($ServerStatus | Select-Object -Property ServerName, Port, Players) -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
                Start-Sleep -Seconds 60
            }
            # Server is now empty and we stop, update and start the server.
            Write-PSFMessage -Level Host -Message "Stopping $ServiceName..." -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target $ServiceName
            Stop-Service -Name $ServiceName
            Write-PSFMessage -Level Host -Message "$($ServiceName): $((Get-Service -Name $ServiceName).Status)." -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target $ServiceName
        } else {
            Write-PSFMessage -Level Host -Message 'Server could not be reached.' -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            Write-PSFMessage -Level Host -Message 'Continuing with updating server.' -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
        }

        Write-PSFMessage -Level Host -Message "Updating $ServiceName..." -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target $ServiceName
        if ($null -ne $Credential) {
            Update-SteamApp -AppID $AppID -Path $Path -Credential $Credential -Arguments "$Arguments" -Force
        } else {
            Update-SteamApp -AppID $AppID -Path $Path -Arguments "$Arguments" -Force
        }

        Write-PSFMessage -Level Host -Message "Starting $ServiceName" -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target $ServiceName
        Start-Service -Name $ServiceName
        Write-PSFMessage -Level Host -Message "$($ServiceName): $((Get-Service -Name $ServiceName).Status)." -Tag 'ServerUpdate' -ModuleName 'SteamPS' -Target $ServiceName

        do {
            $TimeoutCounter++ # Add +1 for every loop.
            Write-PSFMessage -Level Host -Message 'Waiting for server to come online again.' -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            Start-Sleep -Seconds 60
            # Getting new server information.
            $ServerStatus = Get-SteamServerInfo -IPAddress $IPAddress -Port $Port -ErrorAction SilentlyContinue | Select-Object -Property ServerName, Port, Players
            Write-PSFMessage -Level Host -Message $ServerStatus -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            Write-PSFMessage -Level Host -Message "No response from $($IPAddress):$($Port)." -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            Write-PSFMessage -Level Host -Message "TimeoutCounter: $TimeoutCounter/$TimeoutLimit" -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            if ($TimeoutCounter -ge $TimeoutLimit) {
                break
            }
        } until ($null -ne $ServerStatus.ServerName)

        if ($null -ne $ServerStatus.ServerName) {
            Write-PSFMessage -Level Host -Message "$($ServerStatus.ServerName) is now ONLINE." -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            $ServerState = 'ONLINE'
            $Color = 'Green'
        } else {
            Write-PSFMessage -Level Critical -Message "Server seems to be OFFLINE after the update..." -Tag 'ServerStatus' -ModuleName 'SteamPS' -Target "$($IPAddress):$($Port)"
            $ServerState = 'OFFLINE'
            $Color = 'Red'
        }
    } # Process

    end {
        if ($null -ne $DiscordWebhookUri -and ($ServerState -eq 'OFFLINE' -or $AlwaysNotify -eq $true)) {
            # Send Message to Discord about the update.
            $ServerFact = New-DiscordFact -Name 'Game Server Info' -Value $(Get-SteamServerInfo -IPAddress $IPAddress -Port $Port -ErrorAction SilentlyContinue | Select-Object -Property ServerName, IP, Port, Players | Out-String)
            $ServerStateFact = New-DiscordFact -Name 'Server State' -Value $(Write-Output -InputObject "Server is $ServerState!")
            $LogFact = New-DiscordFact -Name 'Log Location' -Value "$LogPath\$ServiceName\$ServiceName-%Date%.csv"
            $Section = New-DiscordSection -Title "$ServiceName - Update Script Executed" -Facts $ServerStateFact, $ServerFact, $LogFact -Color $Color
            Send-DiscordMessage -WebHookUrl $DiscordWebhookUri -Sections $Section
        }
    } # End
} # Cmdlet
#EndRegion '.\Public\Server\Update-SteamServer.ps1' 195