PlayFabMultiplayer.psm1

Set-StrictMode -Version Latest

<#
    .SYNOPSIS
        Internal helper to call a playFab API, wait for its task to complete, and handle some boilerplate error checking/logging of failures.
        Returns the API response (the PlayFabResult<T>.Result obj) on success; throws on errors.
 
    .PARAMETER ApiName
        Used for logging; typically the playfab method name (eg: ListQosServersAsync)
 
    .PARAMETER ApiCall
        The api call in a script block; expected to return a Task with a playfab result.
        eg: { [PlayFab.PlayFabMultiplayerAPI]::ListQosServersAsync($null) }
 
    .PARAMETER SkipTokenCheck
        Optional. Set this switch to bypass the check for an entity token.
        Typically only used for the entity token API call; all other PlayFab APIs require a token.
 
    .PARAMETER WarnIfPaged
        Optional. If set, we check for a NextLink field on a successful response and warn if the results have a next link (paged results).
#>

function CallPlayFabApi
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string] $ApiName,

        [Parameter(Mandatory = $true)]
        [scriptblock] $ApiCall,

        [Parameter(Mandatory = $false)]
        [switch] $SkipTokenCheck = $false,

        [Parameter(Mandatory = $false)]
        [switch] $WarnIfPaged = $false # most calls aren't paged in the first place
    )

    # login / entity token check
    if (-not $SkipTokenCheck -and [string]::IsNullOrEmpty([PlayFab.PlayFabSettings]::TitleId))
    {
        $msg = "Call Get-PFTitleEntityToken first to setup your credentials for a PlayFab title."
        Write-Warning $msg
        throw "PlayFabSettings doesn't have a configured titleId; $msg"
    }

    # send/wait for API response
    $apiTask = & $ApiCall
    $apiTask.Wait()
    
    if ($apiTask.IsFaulted)
    {
        $aggregateEx = $apiTask.Exception.Flatten()
        $errMsg = "PlayFab API $ApiName failed with exceptions:"
        foreach ($ex in $aggregateEx.InnerExceptions)
        {
            $errMsg += $ex.Message
        }
        Write-Warning $errMsg
        throw $ex
    }

    # boilerplate error handling
    $playFabResult = $apiTask.Result

    if ($playFabResult.Error -ne $null)
    {
        $pfError = $PlayFabResult.Error
        $errReport = $pfError.GenerateErrorReport()
        $errMsg = "PlayFab API $ApiName returned Http StatusCode $($pfError.HttpCode) ($($pfError.HttpStatus)); ErrorMsg: $($pfError.ErrorMessage)."
        Write-Warning "$errMsg `n PlayFab Error Report: $errReport"
        throw $errMsg
    }

    $result = $PlayFabResult.Result

    if ($WarnIfPaged -and $result.SkipToken -ne $null)
    {
        Write-Warning "There are many results requiring paging, this operation may take some time."
    }

    return $result
}

function Get-PFMultiplayerQosServer
{
    [CmdletBinding( )]
    Param()
    
    Begin
    {}

    Process
    {
        $qosServersResult = CallPlayFabApi -ApiName ListQosServersAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::ListQosServersAsync($null) }
        return $qosServersResult.QosServers
     }

    <#
.SYNOPSIS
Gets a list of QoS servers.
  
.DESCRIPTION
Returns list of QoS servers to use for network performance measurements and region selection during allocation.
#>

}

function Get-PFTitleEntityToken
{
    [CmdletBinding( )]
    Param(
    [Parameter(Mandatory = $true)][string]$TitleID, 
    [Parameter(Mandatory = $true)][string]$SecretKey 
    )
    
    Begin
    {}

    Process
    {
 
        #Sets the public properties of the API's static class that configure which title target via entity tokens
        [PlayFab.PlayFabSettings]::TitleID = $TitleID
        [PlayFab.PlayFabSettings]::DeveloperSecretKey = $SecretKey

        $key = new-object PlayFab.AuthenticationModels.EntityKey
        $key.ID = $TitleID
        
        $tokenRequest = new-object PlayFab.AuthenticationModels.GetEntityTokenRequest
        $tokenRequest.Entity = $key

        $entityTokenResult = CallPlayFabApi -ApiName GetEntityTokenAsync -ApiCall { [PlayFab.PlayFabAuthenticationAPI]::GetEntityTokenAsync($tokenRequest) } -SkipTokenCheck
       
        return $entityTokenResult
     }
     <#
.SYNOPSIS
Gets an entity token using the provided title and secret key. Required for other Entity API interactions.
  
.DESCRIPTION
Using a secret key generated in Game Manager, this cmdlet generates and entity token for the specified title id. The entity token will be used for authenticating other PlayCompute cmdlets for the length of the PowerShell session.
#>

}

function Get-PFMultiplayerAsset
{
    [CmdletBinding( )]
    Param( 
        [Parameter(Mandatory = $false)][string] $SkipToken)
    
    Begin
    {}

    Process
    {
        $assetReq = NEW-OBJECT PlayFab.MultiplayerModels.ListAssetsRequest
        
        if ($SkipToken -ne "")
        {
            $assetReq.SkipToken = $SkipToken
            $assetReq.NumItems = 10
        }

        $assetsResult = CallPlayFabApi -ApiName ListAssetsAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::ListAssetsAsync($assetReq) } -WarnIfPaged
        $SkipToken = $assetsResult.SkipToken

        if ($SkipToken -ne "")
        {
            write-progress "Getting more assets" 
            $newresult = Get-PFMultiplayerAsset -SkipToken $SkipToken
            $result = $assetsResult.AssetSummaries +  $newresult
            return $result
        }

        return $assetsResult.AssetSummaries
     }

     <#
.SYNOPSIS
  
Gets the game server assets that have been uploaded
  
.DESCRIPTION
  
Gets the game server assets that have been added through Add-PFMultiplayerAssets or GetAssetUploadURL API. Command is run in the context of the title specified using Get-PFTitleEntityToken
#>

}

function Add-PFMultiplayerAsset
{
    [CmdletBinding( )]
    Param(
        [Parameter(Mandatory = $true)]
        [string] $FilePath
    )
    
    Begin
    {}

    Process
    {
        if (-not (Test-Path $FilePath))
        {
            write-error "Provided file path is not valid"
            return
        }

        $assetFileItem = Get-Item $FilePath
        $assetHash = Get-FileHash $FilePath -Algorithm MD5

        $assetReq = NEW-OBJECT PlayFab.MultiplayerModels.GetAssetUploadUrlRequest

        $BlobName = $FilePath | Split-Path -Leaf
        $assetReq.Name = $BlobName

        $metadata = New-Object 'System.Collections.Generic.Dictionary[String,String]'
        $metadata.Add("OriginalFilePath",$FilePath)
        $metadata.Add("sizeBytes", $assetFileItem.Length)
        $metadata.Add("md5", $assetHash.Hash)
        $metadata.Add("uploadTimeUtc", [DateTime]::UtcNow.ToString())
        $assetReq.MetaData = $MetaData

        $assetsResult = CallPlayFabApi -ApiName GetAssetUploadUrlAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::GetAssetUploadUrlAsync($assetReq) }

        $sastoken = $assetsResult.AssetUploadUrl
        $sastoken = $sastoken.Remove($sastoken.LastIndexOf("&api"))
        $storageaccountname = $sastoken.SubString(8,$sastoken.IndexOf("blob")-9)
        $sastoken = $sastoken.substring($sastoken.IndexOf("sv"))

        $accountContext = New-AzureStorageContext -SasToken $sasToken -StorageAccountName   $storageaccountname 
        ## $blob = Get-AzureStorageBlob -Container "gameassets" -Blob $ID -Context $accountContext
        Set-AzureStorageBlobContent -File $FilePath -Container "gameassets" -Context $accountContext -Blob $BlobName
     }

          <#
.SYNOPSIS
Uploads an asset to PlayFab.
  
.DESCRIPTION
Upload an asset (commonly a zip file) by providing a friendly name and file path. This cmdlet uses the GetAssetUploadURl API to get an Azure blob URL, and then uses Azure storage cmdlets to upload the asset.
#>


}

function Add-PFMultiplayerCertificate
{
    [CmdletBinding( )]
    Param(
    [Parameter(Mandatory = $true)][string]$Name, 
    [Parameter(Mandatory = $true)][string]$FilePath,
    [Parameter(Mandatory = $false)][string]$Password 
    )
    
    Begin
    {}

    Process
    {
    
        $PathTest = Test-Path $FilePath
        if ($PathTest -eq $false)
        {
            write-error "Provided file path is not valid"
            return 
        }

        $certificateBytes = [System.IO.File]::ReadAllBytes($FilePath)
        $base64 = [System.Convert]::ToBase64String($certificateBytes)

        $cert = NEW-OBJECT PlayFab.MultiplayerModels.Certificate
        $cert.Base64EncodedValue = $base64 
        $cert.Name = $Name

        if($Password -ne $null)
        {
            $cert.Password = $Password
        }

        $certReq = NEW-OBJECT PlayFab.MultiplayerModels.UploadCertificateRequest
        $certReq.GameCertificate = $cert

        return CallPlayFabApi -ApiName UploadCertificateAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::UploadCertificateAsync($certReq) }
     }

          <#
.SYNOPSIS
Uploads a certificate to PlayFab.
  
.DESCRIPTION
Uploads a certificate to PlayFab for game server usage. Cmdlet does not support certificates with passwords but coming soon.
#>


}

function Get-PFMultiplayerCertificate
{
    [CmdletBinding( )]
    Param()
    
    Begin
    {}

    Process
    {
        $listCertResult = CallPlayFabApi -ApiName ListCertificatesAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::ListCertificatesAsync($null) }
        return $listCertResult.CertificateSummaries
     }

     <#
.SYNOPSIS
  
Gets the game server certificates that have been uploaded
  
.DESCRIPTION
  
Gets the game server Certificate that have been added through Add-PFMultiplayerCertificate.
#>

}

function Get-PFMultiplayerBuild
{
    [CmdletBinding( DefaultParameterSetName="All")]
    Param( 
    [Parameter(ParameterSetName = "SpecificName", Mandatory = $true)][string]$BuildName,
    [Parameter(ParameterSetName = "All")][Switch]$All
    )
    
    Begin
    {}

    Process
    {
        $buildsResult = CallPlayFabApi -ApiName ListBuildsAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::ListBuildsAsync($null) }
        if ($PSCmdlet.ParameterSetName -eq "All")
        {
            return $buildsResult.BuildSummaries
        }

        return $buildsResult.BuildSummaries | Where-Object -Property BuildName -Contains $BuildName
     }

     <#
.SYNOPSIS
  
Gets the game server builds that have been created
  
.DESCRIPTION
  
Gets the game server builds
  
#>

}

function New-PFMultiplayerBuild
{
    [CmdletBinding( )]
    Param(
    [Parameter(Mandatory = $true)][string]$BuildName, 
    [Parameter(Mandatory = $true)][string]$AssetName, 
    [Parameter(Mandatory = $true)][string]$AssetMountPath,
    [Parameter(Mandatory = $true)][string]$StartGameCommand,
    [Parameter(Mandatory = $true)]$MappedPorts,
    [Parameter(Mandatory = $true)]$VMSize,
    [Parameter(Mandatory = $true)]$BuildCerts
    )
    
    Begin
    {}

    Process
    {

        $BuildReq = NEW-OBJECT PlayFab.MultiplayerModels.CreateBuildWithManagedContainerRequest
        $BuildReq.BuildName = $BuildName
        $BuildReq.ContainerFlavor = [PlayFab.MultiplayerModels.ContainerFlavor]::WindowsServerCorePlayFab
 
        $BuildReq.StartGameCommand = $StartGameCommand
        $BuildReq.MappedPorts = $MappedPorts
        $BuildReq.VMSize = $VMSize
        $BuildReq.GameCertificateReferences =  $BuildCerts
 
        $BuildReq.MultiplayerServerCountPerVm = 1 

        $Asset = NEW-OBJECT PlayFab.MultiplayerModels.AssetReferenceParams
        $Asset.Name = $AssetName
        $Asset.MountPath = $AssetMountPath
        $BuildReq.GameAssetReferences = $Asset

        $Regions = NEW-OBJECT PlayFab.MultiplayerModels.BuildRegionParams
        $Regions.MaxSessions = 50
        $Regions.Region = [PlayFab.MultiplayerModels.AzureRegion]::EastUs
        $Regions.StandbySessions = 2
        $BuildReq.RegionConfiguration = $Regions
    
        
        $metadata = New-Object 'System.Collections.Generic.Dictionary[String,String]'
        $metadata.Add("CreatedBy","PowerShell")
        $BuildReq.MetaData = $MetaData

        $buildResult = CallPlayFabApi -ApiName CreateBuildWithManagedContainerAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::CreateBuildWithManagedContainerAsync($BuildReq) }
        return $buildResult.BuildSummary
     }

          <#
.SYNOPSIS
Creates a game server build.
  
.DESCRIPTION
Creates a game server build. Currently hard-coded to create a Windows Server Core build.
  
.EXAMPLE
         
$VMSelection = [PlayFab.MultiplayerModels.AzureVMSize]::Standard_F4
  
$Ports = New-object PlayFab.MultiplayerModels.Port
$Ports.Name = "Test Port"
$Ports.Num = 3055
$Ports.Protocol = [PlayFab.MultiplayerModels.ProtocolType]::UDP
  
$BuildCert = New-Object 'System.Collections.Generic.List[String]'
$Buildcert.Add("WeirdErrorTest")
$Buildcert.Add("FakeCert")
  
New-PFMultiplayerBuild -BuildName "PowerShellTest" -AssetName "HaroRunner" -AssetMountPath "C:\Asset\" -StartGameCommand "C:\Assets\WinTestRunnerGame.exe" -MappedPorts $Ports -VMSize $VMSelection -BuildCerts $BuildCert
  
#>


}

function Remove-PFMultiplayerBuild
{
    [CmdletBinding( )]
    Param(
    [Parameter(Mandatory = $true)][string]$BuildName
    )

    Begin
    {}

    Process
    {
        $BuildID = Get-PFMultiplayerBuild -BuildName $BuildName
        $BuildID = $BuildID.BuildID
        
        $BuildReq = NEW-OBJECT PlayFab.MultiplayerModels.DeleteBuildRequest
        $BuildReq.BuildId = $BuildId

        $result = CallPlayFabApi -ApiName DeleteBuildAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::DeleteBuildAsync($BuildReq) }
        return $result.BuildSummary
     }

          <#
.SYNOPSIS
Deletes a game server build.
  
.DESCRIPTION
Deletes a game server build.
  
.EXAMPLE
  
Remove-PFMultiplayerBuild -BuildId <build_id>
  
#>


}

function Get-PFMultiplayerServer
{
    [CmdletBinding(DefaultParameterSetName="AllRegions")]
    Param(   
    [Parameter(Mandatory = $true)][string]$BuildName,
    [Parameter(ParameterSetName = "SpecificRegion", Mandatory = $True)][PlayFab.MultiplayerModels.AzureRegion]$Region,
    [Parameter(ParameterSetName = "AllRegions")][Switch] $AllRegions
    )
    
    Begin
    {}

    Process
    {
        $BuildID = Get-PFMultiplayerBuild -BuildName $BuildName
        $BuildID = $BuildID.BuildID

        $RegionList = New-Object 'System.Collections.Generic.List[PlayFab.MultiplayerModels.AzureRegion]'
         

        if($PSCmdlet.ParameterSetName -eq "AllRegions")
        {
            $regions = [PlayFab.MultiplayerModels.AzureRegion[]] @("EastUs", "WestUs", "CentralUs", "NorthEurope")
            $RegionList.AddRange($regions)
        }
        else
        {
            $RegionList.Add($Region)
        }

        foreach ($Region in $RegionList)
        {
            $SessionReq = NEW-OBJECT PlayFab.MultiplayerModels.ListMultiplayerServersRequest
            $SessionReq.BuildID = $BuildID
            $SessionReq.Region =  $Region

            $result = CallPlayFabApi -ApiName ListMultiplayerServersAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::ListMultiplayerServersAsync($SessionReq) } -WarnIfPaged
            $unsortedresult = $result.MultiplayerServerSummaries
            $result = $unsortedresult | Sort-Object region, state
            return $result
        }
     }

     <#
.SYNOPSIS
  
Gets the servers for a build
  
.DESCRIPTION
  
Gets the servers for a build and specified region
  
.Example
  
Get servers from a specific build in East US
  
$Region = [PlayFab.MultiplayerModels.AzureRegion]::EastUS
$Name = "ZIP_AllocationTest"
Get-PFMultiplayerServer -BuildName $Name -Region $Region
  
.Example
Get servers from a specific build across all regions
  
  
$Name = "ZIP_AllocationTest"
Get-PFMultiplayerServer -BuildName $Name -AllRegions
  
#>

}

function New-PFMultiplayerServer
{
    [CmdletBinding( )]
    Param(
    [Parameter(Mandatory = $true)][string]$BuildName,
    [Parameter(Mandatory = $true)][string]$SessionId,
    [Parameter(Mandatory = $true)][string]$SessionCookie,
    [Parameter(Mandatory = $true)][System.Collections.Generic.List[PlayFab.MultiplayerModels.AzureRegion]]$PreferredRegions
    )

    Begin
    {}

    Process
    {
        $AllocationReq = NEW-OBJECT PlayFab.MultiplayerModels.RequestMultiplayerServerRequest
        
        $BuildID = Get-PFMultiplayerBuild -BuildName $BuildName
        $BuildID = $BuildID.BuildID

        
        $AllocationReq.BuildId = $BuildId
        $AllocationReq.SessionId = $SessionId
        $AllocationReq.SessionCookie = $SessionCookie
        $AllocationReq.PreferredRegions = $PreferredRegions

        return CallPlayFabApi -ApiName RequestMultiplayerServerAsync -ApiCall { [PlayFab.PlayFabMultiplayerAPI]::RequestMultiplayerServerAsync($AllocationReq) }
    }


     <#
.SYNOPSIS
  
Allocates a new multiplayer server
  
.DESCRIPTION
  
Allocates a new multiplayer server using the specified build and region preferences.
  
.Example
  
$regions = new-object 'System.Collections.Generic.List[PlayFab.MultiplayerModels.AzureRegion]'
$regions.Add("EastUS");
 
New-PFMultiplayerServer -BuildName "MyBuild" -SessionId "00000000-0000-0000-0000-000000000001" -SessionCookie "test cookie" -PreferredRegions $regions
 
#>

}


##Export cmdlet and alias to module

New-Alias GPFQ Get-PFMultiplayerQosServer
Export-ModuleMember -Alias GPFMQ -Function Get-PFMultiplayerQosServer

New-Alias GPFTET Get-PFTitleEntityToken
Export-ModuleMember -Alias GPFTET -Function Get-PFTitleEntityToken

New-Alias GPFGA Get-PFMultiplayerAsset
Export-ModuleMember -Alias GPFMA -Function Get-PFMultiplayerAsset

New-Alias APFGA Add-PFMultiplayerAsset
Export-ModuleMember -Alias APFMA -Function Add-PFMultiplayerAsset

New-Alias APFGC Add-PFMultiplayerCertificate
Export-ModuleMember -Alias APFMC -Function Add-PFMultiplayerCertificate

New-Alias GPFGC Get-PFMultiplayerCertificate
Export-ModuleMember -Alias GPFMC -Function Get-PFMultiplayerCertificate

New-Alias NPFGB New-PFMultiplayerBuild
Export-ModuleMember -Alias NPFMB -Function New-PFMultiplayerBuild


New-Alias RPFGB Remove-PFMultiplayerBuild
Export-ModuleMember -Alias RPFMB -Function Remove-PFMultiplayerBuild

New-Alias GPFGB Get-PFMultiplayerBuild
Export-ModuleMember -Alias GPFMB -Function Get-PFMultiplayerBuild

New-Alias GPFGSH Get-PFMultiplayerServer
Export-ModuleMember -Alias GPFMS -Function Get-PFMultiplayerServer

New-Alias NPFGSH New-PFMultiplayerServer
Export-ModuleMember -Alias NPFMS -Function New-PFMultiplayerServer