MSStore.psm1
|
################################ # Start: Internal use functions ################################ function New-CV() { $cv = [Convert]::ToBase64String([Guid]::NewGuid().ToByteArray(), 0, 12) $cv } function Get-AccessTokenFromSessionData() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState ) $connectionInfo = Get-MSStoreConnectionInfo -SessionState $SessionState $token = Get-AccessToken -ConnectionInfo $connectionInfo $token } function Get-AccessToken() { param( [Parameter(Mandatory = $true)] [PSCustomObject]$ConnectionInfo ) $authCtx = $ConnectionInfo.AuthCtx $credentials = $ConnectionInfo.Credentials $clientId = $ConnectionInfo.ClientId $resource = $ConnectionInfo.Resource $userCredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential"($credentials.Username, $credentials.Password) $token = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync( $AuthCtx, $Resource, $ClientId, $userCredential).Result $token } function Get-MSStoreConnectionInfo { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState ) if ($sessionState.PSVariable -eq $null) { throw "unable to access SessionState.PSVariable, Please call Connect-MSStore before calling any other Powershell CmdLet for the MSStore Module" } $connectionInfo = $sessionState.PSVariable.GetValue("ConnectionInfo"); if ($connectionInfo -eq $null) { throw "Please call Connect-MSStore before calling any other Powershell CmdLet for the WSfB Management Tools Service" } return $connectionInfo } function Get-MSStoreBaseUri() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState ) $connectionInfo = Get-MSStoreConnectionInfo -SessionState $SessionState $connectionInfo.MtsBaseUri } ################################ # End: Internal use functions ################################ ################################ # Start: Exported functions ################################ <# .SYNOPSIS Method to retrieve token for access to MSStore #> function Grant-MSStoreClientAppAccess() { param( [string] $ClientId = "295a96a4-53fa-41ee-9a49-91fb99f95a00", [Uri] $RedirectUri = [uri] "http://localhost/mts/tools", [string] $Resource = "https://onestore.microsoft.com" ) $authorityUrl = "https://login.windows.net/common" $authCtx = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" $authorityUrl $platformParams = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" ([Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always) $token = $authCtx.AcquireTokenAsync($Resource, $ClientId, $RedirectUri, $platformParams).Result if ($token -eq $null) { Write-Error "Unable to properly authorize the client application $($ClientId)" } } <# .SYNOPSIS Method to connect to MSStore with the credentials specified #> function Connect-MSStore() { [CmdletBinding()] param( # Parameter help description [Parameter(Mandatory = $true)] [pscredential] $Credentials, [string] $ClientId = "295a96a4-53fa-41ee-9a49-91fb99f95a00", [Uri] $RedirectUri = [uri] "http://localhost/mts/tools", [string] $Resource = "https://onestore.microsoft.com", [string] $MtsBaseUri = "https://bspmts.mp.microsoft.com" ) $authorityUrl = "https://login.windows.net/common" $authCtx = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" $authorityUrl $connectionInfo = [PSCustomObject]@{ AuthCtx = $authCtx Credentials = $Credentials Resource = $Resource ClientId = $ClientId MtsBaseUri = $MtsBaseUri.TrimEnd("/") # no trailing slashes allowed } $token = Get-AccessToken -ConnectionInfo $connectionInfo if ($token -eq $null) { throw "Unable to retrieve token for user '$($Credentials.Username)', ensure you've allowed access to the client application by calling Grant-MSStoreClientAppAccess" } $sessionState = $PSCmdlet.SessionState $sessionState.PSVariable.Set("ConnectionInfo", $connectionInfo) } <# .SYNOPSIS Method to retrieve applications from tenant's inventory #> function Get-MSStoreInventory() { [CmdletBinding()] param( [string] $ContinuationToken, [switch] $ExcludeOnline, [switch] $ExcludeOffline, [int] $MaxResults = 25, [System.Nullable[DateTime]]$ModifiedSince = $null ) $token = Get-AccessTokenFromSessionData -SessionState $PSCmdlet.SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $PSCmdlet.SessionState $mDollarBaseUri = "https://displaycatalog.mp.microsoft.com" if ($ExcludeOnline -and $ExcludeOffline) { throw "Cannot exclude both online and offline from the inventory query" } $queryParameters = "" if (-not $ExcludeOnline) { $queryParameters += "licenseTypes=Online&" } if (-not $ExcludeOffline) { $queryParameters += "licenseTypes=Offline&" } if (-not [String]::IsNullOrWhiteSpace($ContinuationToken)) { $queryParameters += "continuationtoken=$($ContinuationToken)&" } if ($MaxResults -ne $null) { $queryParameters += "maxResults=$($MaxResults)&" } if ($ModifiedSince -ne $null) { $queryParameters += "modifiedSince=$($ModifiedSince.Value.ToString("O"))&" } $queryParameters = $queryParameters.TrimEnd("&"); $queryParameters += "&IncludeRemoved=false&includeSubscription=true" $restPath = "$mtsBaseUri/V1/Inventory?$queryParameters" $response = Invoke-RestMethod ` -Method GET ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } $productDictionary = @{} $productList = @($response.inventoryEntries | % {$_.productKey.productId}) -join "," $mDollarQueryParameters = Get-QueryString ([ordered]@{ bigIds = $productList market = "US" languages = "en-us" catalogId = "4" fieldsTemplate = "Details" }) $mDollarRestPath = "$mDollarBaseUri/v7.0/products?$mDollarQueryParameters" $mDollarContinuationToken = $null do { $mDollarresponse = Invoke-RestMethod ` -Method GET ` -Uri $mDollarRestPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } foreach ($product in $mDollarresponse.products) { $productDictionary.Add($product.productId, $product.LocalizedProperties.ProductTitle) } $mDollarContinuationToken = $result.ContinuationToken }while (-not ([String]::IsNullOrWhiteSpace($mDollarContinuationToken))) foreach ($inventoryEntry in $response.inventoryEntries) { $inventoryEntry | Add-Member -type NoteProperty -name ProductTitle -value $productDictionary[$inventoryEntry.productKey.productId] $inventoryEntry | Add-Member -type NoteProperty -name ProductId -value $inventoryEntry.productKey.productId $inventoryEntry | Add-Member -type NoteProperty -name SkuId -value $inventoryEntry.productKey.skuId $inventoryEntry.PSObject.Properties.Remove('productKey') $inventoryEntry.PSObject.Properties.Remove('lastModified') $inventoryEntry.PSObject.Properties.Remove('status') $inventoryEntry.PSObject.Properties.Remove('distributionPolicy') } $response.inventoryEntries } function Get-QueryString { param( [System.Collections.Specialized.OrderedDictionary]$Parameters ) if ($Parameters) { @($Parameters.GetEnumerator() | ForEach-Object { $_.Name + '=' + $_.Value }) -join '&' } } function Get-MSStoreSeatAssignments() { [CmdletBinding(DefaultParameterSetName = "Batch")] param( [Parameter(Mandatory = $true)] [string] $ProductId, [Parameter(Mandatory = $true)] [string] $SkuId, [Parameter(ParameterSetName = "Batch")] [ValidateRange(1, 25)] [int]$PageSize = 25 ) $continuationToken = $null do { $result = Get-MtsSeatAssignmentsInternal ` -ProductId $ProductId ` -SkuId $SkuId ` -MaxPageSize $PageSize ` -ContinuationToken $continuationToken ` -SessionState $PSCmdlet.SessionState Write-Output $result.Seats $continuationToken = $result.ContinuationToken } while (-not ([String]::IsNullOrWhiteSpace($continuationToken))) } <# .SYNOPSIS Method to retrieve Seat Assignment details #> function Get-MSStoreSeatAssignmentsInternal() { [CmdletBinding(DefaultParameterSetName = "Base")] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId , [Parameter(ParameterSetName = "Base")] [string] $ContinuationToken, [Parameter(ParameterSetName = "Base")] [int] $MaxPageSize = 25 ) Get-MtsSeatAssignmentsInternal ` -ProductId $ProductId ` -SkuId $SkuId ` -ContinuationToken $ContinuationToken ` -MaxPageSize $MaxPageSize ` -SessionState $PSCmdlet.SessionState } function Get-MtsSeatAssignmentsInternal() { [CmdletBinding(DefaultParameterSetName = "Base")] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId , [Parameter(ParameterSetName = "Base")] [string] $ContinuationToken, [Parameter(ParameterSetName = "Base")] [int] $MaxPageSize = 25, [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState ) $token = Get-AccessTokenFromSessionData -SessionState $SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $SessionState $queryParameters = "" if (-not [String]::IsNullOrWhiteSpace($ContinuationToken)) { $queryParameters += "continuationtoken=$($ContinuationToken)&" } if ($MaxPageSize -ne $null) { $queryParameters += "maxResults=$($MaxPageSize)&" } # get rid of any trailing ampersands $queryParameters = $queryParameters.TrimEnd("&"); $restPath = "$mtsBaseUri/V1/Inventory/$($ProductId)/$($SkuId)/Seats?$($queryParameters)" $response = Invoke-RestMethod ` -Method GET ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } $response } <# .SYNOPSIS Method to assign seats to a user #> function Add-MSStoreSeatAssignment() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Username, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId ) $token = Get-AccessTokenFromSessionData -SessionState $PSCmdlet.SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $PSCmdlet.SessionState $restPath = "$mtsBaseUri/V1/Inventory/$($ProductId)/$($SkuId)/Seats/$($Username)" $response = Invoke-RestMethod ` -Method Post ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } ` -ContentType 'application/json' $response Get-StoreInstallLink $ProductId $SkuId } <# .SYNOPSIS Method to remove seats assignments #> function Remove-MSStoreSeatAssignments() { [CmdletBinding(DefaultParameterSetName = "Usernames")] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId, [Parameter(Mandatory = $false)] [ValidateRange(1, 25)] [int] $BatchSize = 25, [Parameter(Mandatory = $true, ParameterSetName = "Usernames")] [string[]] $Usernames, [Parameter(Mandatory = $true, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $PathToCsv, [Parameter(Mandatory = $false, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $ColumnName = "Username", [switch] $ShowProgress ) if ($PSCmdlet.ParameterSetName -eq "Usernames") { Start-MtsBulkSeatOperation ` -Operation "reclaim" ` -ProductId $ProductId ` -SkuId $SkuId ` -BatchSize $BatchSize ` -Usernames $Usernames ` -ShowProgress:$ShowProgress ` -SessionState $PSCmdlet.SessionState } elseif ($PSCmdlet.ParameterSetName -eq "Csv") { Start-MtsBulkSeatOperation ` -Operation "reclaim" ` -ProductId $ProductId ` -SkuId $SkuId ` -BatchSize $BatchSize ` -PathToCsv $PathToCsv ` -ColumnName $ColumnName ` -ShowProgress:$ShowProgress ` -SessionState $PSCmdlet.SessionState } } <# .SYNOPSIS Method to assign seats to a user #> function Add-MSStoreSeatAssignments() { [CmdletBinding(DefaultParameterSetName = "Usernames")] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId, [Parameter(Mandatory = $false)] [ValidateRange(1, 25)] [int] $BatchSize = 25, [Parameter(Mandatory = $true, ParameterSetName = "Usernames")] [string[]] $Usernames, [Parameter(Mandatory = $true, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $PathToCsv, [Parameter(Mandatory = $false, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $ColumnName = "Username", [switch] $ShowProgress ) if ($PSCmdlet.ParameterSetName -eq "Usernames") { Start-MtsBulkSeatOperation ` -Operation "assign" ` -ProductId $ProductId ` -SkuId $SkuId ` -BatchSize $BatchSize ` -Usernames $Usernames ` -ShowProgress:$ShowProgress ` -SessionState $PSCmdlet.SessionState } elseif ($PSCmdlet.ParameterSetName -eq "Csv") { Start-MtsBulkSeatOperation ` -Operation "assign" ` -ProductId $ProductId ` -SkuId $SkuId ` -BatchSize $BatchSize ` -PathToCsv $PathToCsv ` -ColumnName $ColumnName ` -ShowProgress:$ShowProgress ` -SessionState $PSCmdlet.SessionState } Get-StoreInstallLink $ProductId $SkuId } function Start-MtsBulkSeatOperation() { [CmdletBinding(DefaultParameterSetName = "Usernames")] param( [Parameter(Mandatory = $true)] [string] $Operation, [Parameter(Mandatory = $true)] [string] $ProductId, [Parameter(Mandatory = $true)] [string] $SkuId, [Parameter(Mandatory = $false)] [ValidateRange(1, 25)] [int] $BatchSize = 25, [Parameter(Mandatory = $true, ParameterSetName = "Usernames")] [string[]] $Usernames, [Parameter(Mandatory = $true, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $PathToCsv, [Parameter(Mandatory = $false, ParameterSetName = "Csv")] [ValidateNotNullOrEmpty()] [string] $ColumnName = "Username", [switch] $ShowProgress, [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState ) process { $_enforceSingleCall = $false $usernamesToProcess = $null if ($PSCmdlet.ParameterSetName -eq "Usernames") { if ($Usernames -eq $null -or $Usernames.Length -eq 0) { throw "At least one username must be specified in the Usernames parameter" } if ($_enforceSingleCall -and $Usernames.Length -gt 25) { throw "The maximum number of assignments in one call is 25." } $usernamesToProcess = [string[]] ($Usernames | ? {-not [String]::IsNullOrWhiteSpace($_)}) } elseif ($PSCmdlet.ParameterSetName -eq "Csv") { $inputData = Import-Csv -Path $PathToCsv $usernamesToProcess = [string[]] ($inputData | % { $_.$ColumnName}) # read the Csv # take only entries which have a "Username" column in them } $processedItemCount = 0; while ($processedItemCount -lt $usernamesToProcess.Length) { if ($ShowProgress) { Write-Progress -Activity "Bulk Operation" -Status "Percent complete:" -PercentComplete (($processedItemCount / $usernamesToProcess.Length) * 100) } $currentBatch = [string[]]($usernamesToProcess | select-object -Skip $processedItemCount -First $BatchSize) # get a new token on each "batch" to ensure it's not expired $token = Get-AccessTokenFromSessionData -SessionState $SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $SessionState $body = @{ usernames = $currentBatch seatAction = $Operation } $restPath = "$mtsBaseUri/V1/Inventory/$($ProductId)/$($SkuId)/Seats" $response = Invoke-RestMethod ` -Method Post ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } ` -Body ($body | ConvertTo-Json) ` -ContentType 'application/json' # process bulk response into individual items $successfulAssignments = [object[]]$response.SeatDetails $failedAssignments = [object[]]$response.FailedSeatOperations if ($successfulAssignments -ne $null) { foreach ($successfulAssignment in $successfulAssignments) { Write-Output (New-Object psobject($successfulAssignment) -Property @{ Result = "Succeeded" }) } } if ($failedAssignments -ne $null) { foreach ($failedAssignment in $failedAssignments) { Write-Output (New-Object psobject($failedAssignment) -Property @{ Result = "Failed" }) } } $processedItemCount += $currentBatch.Length if ($ShowProgress) { Write-Progress -Activity "Bulk Operation" -Status "Percent complete:" -PercentComplete (($processedItemCount / $usernamesToProcess.Length) * 100) } } } } function Remove-MSStoreSeatAssignmentsLegacy() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateCount(1, 25)] [string[]] $Usernames, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId ) $token = Get-AccessTokenFromSessionData -SessionState $PSCmdlet.SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $PSCmdlet.SessionState $body = @{ usernames = $Usernames seatAction = "reclaim" } $restPath = "$mtsBaseUri/V1/Inventory/$($ProductId)/$($SkuId)/Seats" $response = Invoke-RestMethod ` -Method Post ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } ` -Body ($body | ConvertTo-Json) ` -ContentType 'application/json' $response } <# .SYNOPSIS Method to remove seats assigned to a user #> function Remove-MSStoreSeatAssignment() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Username, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId ) $token = Get-AccessTokenFromSessionData -SessionState $PSCmdlet.SessionState $cv = New-CV $mtsBaseUri = Get-MSStoreBaseUri -SessionState $PSCmdlet.SessionState $restPath = "$mtsBaseUri/V1/Inventory/$($ProductId)/$($SkuId)/Seats/$($Username)" $response = Invoke-RestMethod ` -Method Delete ` -Uri $restPath ` -Headers @{ "MS-CV" = $cv "Authorization" = "Bearer $($token.AccessToken)" } $response } <# .SYNOPSIS Method to retrieve install link for given product and sku #> function Get-StoreInstallLink() { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SkuId ) write-host "You can provide this URL to your users in order to install this app from the store:" $installLink = "https://businessstore.microsoft.com/en-us/AppInstall?productId=" + $ProductId + "&skuId=" + $SkuId + "&catalogId=4" write-host $installLink `n`n } ################################ # End: Exported functions ################################ Write-Host "WSfB Management Tools Powershell module loaded" |