OnPremisesDataGatewayHAMgmt.psm1
If ($PSVersionTable.PSVersion.Major -lt 5) { Write-Host "Current powershell Version is " + $PSVersionTable.PSVersion + ", version 5 is required for this module." return } $script:nullString = (New-Guid).Guid [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Add-Type -TypeDefinition @" public enum LoadBalancingType { Failover = 0, Random = 1 } "@ <# .SYNOPSIS Get default environment. .DESCRIPTION This function get environment from on-premises data gateway configuration. #> Function Get-DefaultEnvironment { $script:adClientId = ($settings | ? { $_.name -eq "AzureADClientID" }).value if ($script:adClientId -eq $null) { $script:adClientId = "ea0616ba-638b-4df5-95b9-636659ae5121" } $script:adAuthority = ($settings | ? { $_.name -eq "AzureADAuthorityAddress" }).value $script:adRedirect = ($settings | ? { $_.name -eq "AzureADRedirectAddress" }).value if ($script:adRedirect -eq $null) { $script:adRedirect = "urn:ietf:wg:oauth:2.0:oob" } $script:adResource = ($settings | ? { $_.name -eq "AzureADResource" }).value $script:gsEndpoint = ($settings | ? { $_.name -eq "GlobalServiceEndpoint" }).value $script:gsBackendUriOverride = ($settings | ? { $_.name -eq "BackendUriOverride" }).value } <# .SYNOPSIS Get email properties. .DESCRIPTION This function setup environment by getting email properties from global service. .PARAMETER EmailAddress The email address. #> Function Get-EmailProperties( [Parameter(Mandatory=$true)] [string]$EmailAddress) { try { $uri = $script:gsEndpoint + "/powerbi/globalservice/v201606/environments/discover?user=" + $EmailAddress $epResponse = Invoke-WebRequest -Uri $uri -Method Post $cloudEnv = $epResponse.Content | ConvertFrom-Json $gateway = $cloudEnv.clients | ? { $_.name -eq "powerbi-gateway" } $pbiBackend = $cloudEnv.services | ? { $_.name -eq "powerbi-backend" } $aadBackend = $cloudEnv.services | ? { $_.name -eq "aad" } $script:adClientId = $gateway.appId $script:adAuthority = $aadBackend.endpoint $script:adRedirect = $gateway.redirectUri $script:adResource = $pbiBackend.resourceId $script:gsEndpoint = $pbiBackend.endpoint return $true } catch { Write-WebRequestFailure return $false } } <# .SYNOPSIS Get response from web exception. .DESCRIPTION This function return response from web exception. .PARAMETER Exception The web exception. #> Function Get-ResponseFromWebException() { If ($_.Exception.Response) { $result = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($result) return $reader.ReadToEnd() } } <# .SYNOPSIS Write web request failure. .DESCRIPTION This function write web request failure to host. #> Function Write-WebRequestFailure() { If ($_.ErrorDetails.Message) { try { If (($_.ErrorDetails.Message | ConvertFrom-Json).error.code -eq "TokenExpired") { Write-Warning "Token expired, please login again." return } } catch { # Ignore json convert exception } } Write-Warning $_.Exception.Message $responseBody = Get-ResponseFromWebException If ($responseBody) { Write-Warning $responseBody } } <# .SYNOPSIS Clean up user account. .DESCRIPTION This function clean up token, backend uri and gateway regions. #> Function Remove-OnPremisesDataGatewayUserAccount() { $script:token = $null $script:backendUri = $null $script:selectedBackend = $null $script:allRegions = $null } <# .SYNOPSIS Set up user account. .DESCRIPTION This function read setting from on-premises data gateway configuration, get token from active directory and get backend uri from global service. .PARAMETER EmailAddress The email address. #> Function Set-OnPremisesDataGatewayUserAccount( [Parameter(Mandatory=$true)] [string]$EmailAddress) { $GWDir = "$PSScriptRoot" $configPath = Join-Path "$GWDir" "\Microsoft.PowerBI.DataMovement.GatewayCommon.dll.config" $config = [xml](gc $configPath) $settings = $config.configuration.applicationSettings.'Microsoft.PowerBI.DataMovement.GatewayCommon.Properties.GatewayCommonSettings'.setting $EmailDiscoveryDisable = ($settings | ? { $_.name -eq "EmailDiscoveryDisable" }).value $script:gsEndpoint = ($settings | ? { $_.name -eq "GlobalServiceEndpoint" }).value # Get environment If ($EmailDiscoveryDisable) { Get-DefaultEnvironment } else { $script:gsBackendUriOverride = $null $r = Get-EmailProperties $EmailAddress if (!$r) { Write-Warning "Get email properties from global service failed, will use default environment." Get-DefaultEnvironment } } if ($script:adResource -eq $null -or $script:gsEndpoint -eq $null) { Write-Error "Read AD resource and global service endpoint failed, make sure install gateway first or provide correct install directory!" return } # Get token Add-Type -Path $( join-path $GWDir "Microsoft.IdentityModel.Clients.ActiveDirectory.dll") Add-Type -Path $( join-path $GWDir "Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll") Add-Type -Path $( join-path $GWDir "Microsoft.PowerBI.DataMovement.GatewayRegions.dll") Add-Type -Path $( join-path $GWDir "Newtonsoft.Json.dll") $script:token = Get-Token If (!$script:token) { Write-Warning "Acquire token failed." return } # Get backend uri If ($script:gsBackendUriOverride -ne $null) { $script:backendUri = $script:gsBackendUriOverride } else { $clusterUri = $script:gsEndpoint + "/spglobalservice/GetOrInsertClusterUrisByTenantLocation" $gsResponse = Invoke-Webrequest -Uri $clusterUri -Headers @{Authorization = "Bearer " + $token.AccessToken} -Method Put $gsResponse = $gsResponse.Content | ConvertFrom-Json $script:backendUri = $gsResponse.FixedClusterUri } $script:selectedBackend = $script:backendUri Write-Host -ForegroundColor Green "Current backend is: " $script:selectedBackend } <# .SYNOPSIS Get token from AD. .DESCRIPTION Get token from AD. #> Function Get-Token(){ $context=[Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($script:adAuthority); $queryParams = "prompt=select_account&msafed=0&login_hint=" + $script:emailAddress $userId = [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::AnyUser return $context.AcquireToken($script:adResource, $script:adClientId, $script:adRedirect, 0, $userId, $queryParams) } <# .SYNOPSIS Get gateway regions from CDN. .DESCRIPTION Get gateway regions from CDN. #> Function Get-Regions() { $script:allRegions = [Microsoft.PowerBI.DataMovement.GatewayRegions.GatewayRegionConfiguration]::GetRegionsAllowedForTenant($script:backendUri) } <# .SYNOPSIS Get gateway regions. .DESCRIPTION Get gateway regions allowed for tenant. #> Function Get-OnPremisesDataGatewayRegions() { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } Get-Regions If ($script:allRegions.Count) { Write-Host -ForegroundColor Green "Available regions:" ForEach ($region in $script:allRegions) { Write-Host $region.Region } } Else { Write-Host -ForegroundColor Green "No region available." } } <# .SYNOPSIS Set gateway region. .DESCRIPTION Set gateway region, will set to default region if this value is $null. .PARAMETER Region The gateway region. #> Function Set-OnPremisesDataGatewayRegion([string]$Region) { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } If ($Region) { $selectedRegion = $script:allRegions | ? { $_.Region -eq $Region } If (!$selectedRegion) { Write-Warning "Invalid region." return } $script:selectedBackend = $selectedRegion.BackendUri.ToString() } Else { $script:selectedBackend = $script:backendUri } Write-Host -ForegroundColor Green "Current backend is: " $script:selectedBackend } <# .SYNOPSIS Get gateway clusters. .DESCRIPTION This function read all gateway clusters owned by the user. #> Function Get-OnPremisesDataGatewayClusters(){ If (!$script:selectedBackend) { Write-Warning "Please log in first." return } try { $gatewayclusters = Invoke-Webrequest -Uri ($script:selectedBackend + "/unifiedgateway/gatewayclusters") -Headers @{Authorization = "Bearer " + $script:token.AccessToken} $gatewayclusters = $gatewayclusters.Content | ConvertFrom-Json ForEach ($cluster in $gatewayclusters) { $cluster.PSObject.Properties.Remove("annotation") $cluster.PSObject.Properties.Remove("publickey") $cluster.PSObject.Properties.Remove("keyword") $cluster.PSObject.Properties.Remove("metadata") $cluster.PSObject.Properties.Remove("gatewayId") $cluster = Set-ClusterLoadBalancingType -ClusterObject $cluster ForEach ($gw in $cluster.gateways) { $gwAnnotation = $gw.gatewayAnnotation | ConvertFrom-Json $gw | Add-Member -MemberType NoteProperty -Name gatewayContactInformation -Value ([System.String]::Join(" ", $gwAnnotation.gatewayContactInformation)) $gw | Add-Member -MemberType NoteProperty -Name gatewayMachine -Value $gwAnnotation.gatewayMachine $gw.PSObject.Properties.Remove("gatewayAnnotation") $gw.PSObject.Properties.Remove("gatewayStaticCapabilities") $gw.PSObject.Properties.Remove("gatewayLoadBalancingSettings") } $cluster.gateways = $cluster.gateways | ConvertTo-Json $cluster.permission = $cluster.permission | ConvertTo-Json } return $gatewayclusters } catch { Write-WebRequestFailure } } <# .SYNOPSIS Get gateway clusters without skipping annotation and static capabilities. .DESCRIPTION This function read all gateway clusters owned by the user. #> Function Get-OnPremisesDataGatewayClustersInternal(){ If (!$script:selectedBackend) { Write-Warning "Please log in first." return } try { $gatewayclusters = Invoke-Webrequest -Uri ($script:selectedBackend + "/unifiedgateway/gatewayclusters") -Headers @{Authorization = "Bearer " + $script:token.AccessToken} $gatewayclusters = $gatewayclusters.Content | ConvertFrom-Json return $gatewayclusters } catch { Write-WebRequestFailure } } <# .SYNOPSIS Get gateway status. .DESCRIPTION This function get gateway status. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER GatewayObjectId The gateway objectId. #> Function Get-OnPremisesDataGatewayStatus( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [Parameter(Mandatory=$true)] [Guid] $GatewayObjectId){ If (!$script:selectedBackend) { Write-Warning "Please log in first." return } try { $gatewayStatus = Invoke-Webrequest -Uri ($script:selectedBackend + "/unifiedgateway/gatewayclusters/" + $ClusterObjectId + "/gateways/" + $GatewayObjectId + "/status") -Headers @{Authorization = "Bearer " + $script:token.AccessToken} $gatewayStatus = $gatewayStatus.Content | ConvertFrom-Json return $gatewayStatus } catch { $response = Get-ResponseFromWebException $_.Exception | ConvertFrom-Json If ($response.error.code -eq "DM_GWPipeline_Client_GatewayUnreachable") { $gwObj = New-Object -TypeName PSObject -Property ([ordered]@{gatewayStatus = "Unreachable"; gatewayVersion = "Unknown"; gatewayUpgradeState = "Unknown"}) return $gwObj } Else { Write-Warning $_.Exception.Message If ($responseBody) { Write-Warning $responseBody } } } } <# .SYNOPSIS Set gateway info. .DESCRIPTION This function set gateway info and return the updated info, note it won't update the field if it's value is null. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER GatewayObjectId The gateway objectId. .PARAMETER MemberStatus The cluster member status. .PARAMETER GatewayContactInformation The gateway contact information. .PARAMETER Name The gateway name. #> Function Set-OnPremisesDataGateway( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [Parameter(Mandatory=$true)] [Guid] $GatewayObjectId, [ValidateSet("None","Enabled")] [string] $MemberStatus = $script:nullString, [string] $GatewayContactInformation = $script:nullString, [string] $Name = $script:nullString) { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } $request = @{} If ($MemberStatus -ne $script:nullString) { $request.Add("clusterMemberStatus", $MemberStatus)} If ($Name -ne $script:nullString) { If ($Name.Trim() -eq '') { Write-Warning 'Gateway name cannot be empty.' return } $request.Add("gatewayName",$Name) } If ($GatewayContactInformation -ne $script:nullString) { $annotation = Create-GatewayAnnotation $ClusterObjectId $GatewayObjectId $GatewayContactInformation If ($annotation) { $request.Add("gatewayAnnotation",$annotation) } Else { return } } $json = $request | ConvertTo-Json $uri = $script:selectedBackend + "/unifiedgateway/gatewayclusters/" + $ClusterObjectId + "/gateways/" + $GatewayObjectId try { $response = Invoke-Webrequest -Uri $uri -Headers @{Authorization = "Bearer " + $script:token.AccessToken} -Method Patch -Body $json -ContentType 'application/json' $response = $response.Content | ConvertFrom-Json $response.PSObject.Properties.Remove("gatewayStaticCapabilities") $response.PSObject.Properties.Remove("gatewayStatus") #Extract contact info and machine from annotation $gwAnnotation = $response.gatewayAnnotation | ConvertFrom-Json $response | Add-Member -MemberType NoteProperty -Name gatewayContactInformation -Value ([System.String]::Join(" ", $gwAnnotation.gatewayContactInformation)) $response | Add-Member -MemberType NoteProperty -Name gatewayMachine -Value $gwAnnotation.gatewayMachine $response.PSObject.Properties.Remove("gatewayAnnotation") $response.PSObject.Properties.Remove("gatewayLoadBalancingSettings") return $response } catch { Write-WebRequestFailure } } <# .SYNOPSIS Get gateway. .DESCRIPTION This function get gateway by searching cluster and gateway in all clusters. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER GatewayObjectId The gateway objectId. #> Function Get-OnPremisesDataGateway( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [Parameter(Mandatory=$true)] [Guid] $GatewayObjectId) { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } $clusters = Get-OnPremisesDataGatewayClustersInternal $cluster = $clusters | Where-Object { $_.objectId -eq $ClusterObjectId } If (!$cluster) { Write-Host -ForegroundColor Red "Invalid cluster object Id" return } $gateway = $cluster.gateways | Where-Object { $_.gatewayObjectId -eq $GatewayObjectId } If (!$gateway) { Write-Host -ForegroundColor Red "Invalid gateway object Id" return } return $gateway } <# .SYNOPSIS Create gateway annotation. .DESCRIPTION This function get gateway annotation then fill with given contact information and machine name. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER GatewayObjectId The gateway objectId. .PARAMETER GatewayContactInformation The gateway contact information. #> Function Create-GatewayAnnotation( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [Parameter(Mandatory=$true)] [Guid] $GatewayObjectId, [string] $GatewayContactInformation = $script:nullString) { $gateway = Get-OnPremisesDataGateway $ClusterObjectId $GatewayObjectId If ($gateway) { $gwAnnotation = $gateway.gatewayAnnotation | ConvertFrom-Json If ($GatewayContactInformation -ne $script:nullString) { $gwAnnotation.gatewayContactInformation = @($GatewayContactInformation) } return $gwAnnotation | ConvertTo-Json } } <# .SYNOPSIS Delete gateway. .DESCRIPTION This function delete gateway, note it will return success for nonexistent gateway. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER GatewayObjectId The gateway objectId. #> Function Remove-OnPremisesDataGateway( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [Parameter(Mandatory=$true)] [Guid] $GatewayObjectId) { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } try { $uri = $script:selectedBackend + "/unifiedgateway/gatewayclusters/" + $ClusterObjectId + "/gateways/" + $GatewayObjectId $response = Invoke-Webrequest -Uri $uri -Headers @{Authorization = "Bearer " + $script:token.AccessToken} -Method Delete $response = $response.Content | ConvertFrom-Json return $response } catch { Write-WebRequestFailure } } <# .SYNOPSIS Set gateway cluster info. .DESCRIPTION This function set gateway cluster info and return the updated info, note it won't update the field if it's value is null. .PARAMETER ClusterObjectId The cluster object Id. .PARAMETER Name The cluster name. .PARAMETER Description The cluster description. #> Function Set-OnPremisesDataGatewayCluster( [Parameter(Mandatory=$true)] [Guid] $ClusterObjectId, [string] $Name = $script:nullString, [string] $Description = $script:nullString, [ValidateSet("Failover","Random")] [string] $LoadBalancingType = $script:nullString) { If (!$script:selectedBackend) { Write-Warning "Please log in first." return } $request = @{} If ($Name -ne $script:nullString) { If ($Name.Trim() -eq '') { Write-Warning 'Cluster name cannot be empty.' return } $request.Add("name",$Name) } If ($Description -ne $script:nullString) { $request.Add("description",$Description) } If ($LoadBalancingType -ne $script:nullString) { $loadBalancingClusterSettings = @{} $type = $LoadBalancingType -as [LoadBalancingType] $loadBalancingClusterSettings.Add("selector", [int]$type) $loadBalancingClusterSettingsJson = $loadBalancingClusterSettings | ConvertTo-Json -Compress $request.Add("loadBalancingSettings", $loadBalancingClusterSettingsJson) } $json = $request | ConvertTo-Json $uri = $script:selectedBackend + "/unifiedgateway/gatewayclusters/" + $ClusterObjectId try { $response = Invoke-Webrequest -Uri $uri -Headers @{Authorization = "Bearer " + $script:token.AccessToken} -Method Patch -Body $json -ContentType 'application/json' $response = $response.Content | ConvertFrom-Json $response.PSObject.Properties.Remove("annotation") $response = Set-ClusterLoadBalancingType -ClusterObject $response return $response } catch { Write-WebRequestFailure } } <# .SYNOPSIS Get all gateway info in cluster. .DESCRIPTION This function list all gateways in cluster. .PARAMETER ClusterObjectId The cluster object Id. #> Function Get-OnPremisesDataClusterGateways( [Parameter(Mandatory=$true)][Guid] $ClusterObjectId) { $clusters = Get-OnPremisesDataGatewayClustersInternal If ($clusters -eq $null) { return } $cluster = $clusters | Where-Object { $_.objectId -eq $ClusterObjectId } $gateways = New-Object System.Collections.ArrayList If ($cluster -eq $null) { Write-Host -ForegroundColor Red "Invalid cluster object Id" return } ForEach ($gateway in $cluster.gateways) { $gwStatus = Get-OnPremisesDataGatewayStatus $cluster.objectId $gateway.gatewayObjectId $gwAnnotation = $gateway.gatewayAnnotation | ConvertFrom-Json $gwObj = New-Object PSObject -Property ( [ordered]@{ gatewayId = $gateway.gatewayId; gatewayObjectId = $gateway.gatewayObjectId; gatewayName = $gateway.gatewayName; isAnchorGateway = $gateway.isAnchorGateway; gatewayStatus = $gwStatus.gatewayStatus; gatewayVersion = $gwStatus.gatewayVersion; gatewayUpgradeState = $gwStatus.gatewayUpgradeState; gatewayClusterStatus = $gateway.gatewayClusterStatus; gatewayMachine = $gwAnnotation.gatewayMachine; }) $gateways.Add($gwObj) > $null } return $gateways } <# .SYNOPSIS Set load balancing type for cluster object. .DESCRIPTION This function sets load balancing type for cluster object. .PARAMETER ClusterObject The cluster object. #> Function Set-ClusterLoadBalancingType( [Parameter(Mandatory=$true)][PSObject] $ClusterObject) { $loadBalancingType = [LoadBalancingType]::Failover If ($ClusterObject.loadBalancingSettings) { $loadBalancingSettings = $ClusterObject.loadBalancingSettings | ConvertFrom-Json $type = $loadBalancingSettings.selector -as [LoadBalancingType] If ($type -is [LoadBalancingType]) { $loadBalancingType = $type } } $ClusterObject | Add-Member -Name loadBalancingType -Value $loadBalancingType -MemberType NoteProperty $ClusterObject.PSObject.Properties.Remove("loadBalancingSettings") return $ClusterObject } Set-Alias Login-OnPremisesDataGateway Set-OnPremisesDataGatewayUserAccount Set-Alias Logout-OnPremisesDataGateway Remove-OnPremisesDataGatewayUserAccount |