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 { # 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' 43 #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\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 Returns the friend list of any Steam user, provided their Steam Community profile visibility is set to "Public". .PARAMETER SteamID64 64 bit Steam ID to return friend list for. .PARAMETER Relationship Relationship filter. Possibles values: all, friend. .PARAMETER OutputFormat Format of the output. Options are json (default), xml or vdf. .EXAMPLE Get-SteamFriendList -SteamID64 76561197960435530 Outputs the user's friends list, as an array of friends. .EXAMPLE Get-SteamFriendList -SteamID64 76561197960435530 | ConvertFrom-Json Outputs the user's friends list, as an array of friends and converts it from Json to PSCustomObjects. .INPUTS int64 .OUTPUTS Returns a string that is either formatted as json, xml or vdf. The user's friends list, as an array of friends. Nothing will be returned if the profile is private. In the array, the following three properties are returned: - steamid: 64 bit Steam ID of the friend. - relationship: Relationship qualifier. - friend_since: Unix timestamp of the time when the relationship was created. .NOTES Author: Frederik Hjorslev Nylander .LINK https://hjorslev.github.io/SteamPS/Get-SteamFriendList.html #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, HelpMessage = '64 bit Steam ID to return friend list for.', ValueFromPipelineByPropertyName = $true)] [int64]$SteamID64, [Parameter(Mandatory = $false, HelpMessage = 'Relationship filter. Possibles values: all, friend.')] [ValidateSet('all', 'friend')] [string]$Relationship, [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/GetFriendList/v1/?key=$(Get-SteamAPIKey)&steamid=$SteamID64&relationship=friend&format=$OutputFormat" -UseBasicParsing Write-Output -InputObject $Request.Content } # Process end { Write-Verbose -Message "[END ] Ending: $($MyInvocation.MyCommand)" } } # Cmdlet #EndRegion '.\Public\API\Get-SteamFriendList.ps1' 83 #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 |