Private/_Helpers.ps1
function Set-ColourOption( [string]$value ) { if ($value -eq "False") { return "White" } elseif ($value -eq "Error") { return "Red" } elseif ($value -eq "Optional") { return "Magenta" } else { return "Green" } } function Test-InsideDockerContainer { $DockerSvc = Get-Service -Name cexecsvc -ErrorAction SilentlyContinue if ($null -eq $DockerSvc ) { return $false } else { return $true } } function Update-Manifest { #.Synopsis # Update a PowerShell module manifest #.Description # By default Update-Manifest increments the ModuleVersion, but it can set any key in the Module Manifest, its PrivateData, or the PSData in PrivateData. # # NOTE: This cannot currently create new keys, or uncomment keys. #.Example # Update-Manifest .\Configuration.psd1 # # Increments the Build part of the ModuleVersion in the Configuration.psd1 file #.Example # Update-Manifest .\Configuration.psd1 -Increment Major # # Increments the Major version part of the ModuleVersion in the Configuration.psd1 file #.Example # Update-Manifest .\Configuration.psd1 -Value '0.4' # # Sets the ModuleVersion in the Configuration.psd1 file to 0.4 #.Example # Update-Manifest .\Configuration.psd1 -Property ReleaseNotes -Value 'Add the awesome Update-Manifest function!' # # Sets the PrivateData.PSData.ReleaseNotes value in the Configuration.psd1 file! [CmdletBinding()] param( # The path to the module manifest file [Parameter(ValueFromPipelineByPropertyName = "True", Position = 0)] [Alias("PSPath")] [string]$Manifest, # The property to be set in the manifest. It must already exist in the file (and not be commented out) # This searches the Manifest root properties, then the properties PrivateData, then the PSData [Parameter(ParameterSetName = "Overwrite")] [string]$PropertyName = 'ModuleVersion', # A new value for the property [Parameter(ParameterSetName = "Overwrite", Mandatory)] $Value, # By default Update-Manifest increments ModuleVersion; this controls which part of the version number is incremented [Parameter(ParameterSetName = "Increment")] [ValidateSet("Major", "Minor", "Build", "Revision")] [string]$Increment = "Build", # When set, and incrementing the ModuleVersion, output the new version number. [Parameter(ParameterSetName = "Increment")] [switch]$Passthru ) $KeyValue = Get-ManifestValue $Manifest -PropertyName $PropertyName -Passthru if ($PSCmdlet.ParameterSetName -eq "Increment") { $Version = [Version]$KeyValue.SafeGetValue() $Version = switch ($Increment) { "Major" { [Version]::new($Version.Major + 1, 0) } "Minor" { $Minor = if ($Version.Minor -le 0) { 1 } else { $Version.Minor + 1 } [Version]::new($Version.Major, $Minor) } "Build" { $Build = if ($Version.Build -le 0) { 1 } else { $Version.Build + 1 } [Version]::new($Version.Major, $Version.Minor, $Build) } "Revision" { $Revision = if ($Version.Revision -le 0) { 1 } else { $Version.Revision + 1 } [Version]::new($Version.Major, $Version.Minor, $Version.Build, $Revision) } } $Value = $Version if ($Passthru) { $Value } } $Value = ConvertTo-Metadata $Value $Extent = $KeyValue.Extent while ($KeyValue.parent) { $KeyValue = $KeyValue.parent } $ManifestContent = $KeyValue.Extent.Text.Remove( $Extent.StartOffset, ($Extent.EndOffset - $Extent.StartOffset) ).Insert($Extent.StartOffset, $Value) if (Test-Path $Manifest) { Set-Content $Manifest $ManifestContent } else { $ManifestContent } } function Get-ManifestValue { #.Synopsis # Reads a specific value from a module manifest #.Description # By default Get-ManifestValue gets the ModuleVersion, but it can read any key in the Module Manifest, including the PrivateData, or the PSData inside the PrivateData. #.Example # Get-ManifestValue .\Configuration.psd1 # # Returns the module version number (as a string) #.Example # Get-ManifestValue .\Configuration.psd1 ReleaseNotes # # Returns the release notes! [CmdletBinding()] param( # The path to the module manifest file [Parameter(ValueFromPipelineByPropertyName = "True", Position = 0)] [Alias("PSPath")] [string]$Manifest, # The property to be read from the manifest. Get-ManifestValue searches the Manifest root properties, then the properties PrivateData, then the PSData [Parameter(ParameterSetName = "Overwrite", Position = 1)] [string]$PropertyName = 'ModuleVersion', [switch]$Passthru ) $ErrorActionPreference = "Stop" if (Test-Path $Manifest) { $ManifestContent = Get-Content $Manifest -Raw } else { $ManifestContent = $Manifest } $Tokens = $Null; $ParseErrors = $Null $AST = [System.Management.Automation.Language.Parser]::ParseInput( $ManifestContent, $Manifest, [ref]$Tokens, [ref]$ParseErrors ) $ManifestHash = $AST.Find( { $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $true ) $KeyValue = $ManifestHash.KeyValuePairs.Where{ $_.Item1.Value -eq $PropertyName }.Item2 # Recursively search for PropertyName in the PrivateData and PrivateData.PSData if (!$KeyValue) { $global:devops_PrivateData = $ManifestHash.KeyValuePairs.Where{ $_.Item1.Value -eq 'PrivateData' }.Item2.PipelineElements.Expression $KeyValue = $PrivateData.KeyValuePairs.Where{ $_.Item1.Value -eq $PropertyName }.Item2 if (!$KeyValue) { $global:devops_PSData = $PrivateData.KeyValuePairs.Where{ $_.Item1.Value -eq 'PSData' }.Item2.PipelineElements.Expression $KeyValue = $PSData.KeyValuePairs.Where{ $(Write-Verbose "'$($_.Item1.Value)' -eq '$PropertyName'"); $_.Item1.Value -eq $PropertyName }.Item2 if (!$KeyValue) { Write-Error "Couldn't find '$PropertyName' to update in '$(Convert-Path $ManifestPath)'" return } } } if ($Passthru) { $KeyValue } else { $KeyValue.SafeGetValue() } } function Invoke-Menu() { Param( [Parameter(Mandatory = $True)][String]$MenuTitle, [Parameter(Mandatory = $True)][array]$MenuOptions ) $MaxValue = $MenuOptions.count - 1 $Selection = 0 $EnterPressed = $False $Filter = "" $MasterMenuList = $MenuOptions if (!$VerbosePreference -eq "Continue") { Clear-Host } While ($EnterPressed -eq $False) { Write-Host "$MenuTitle" if ($Filter.Length -gt 0) { $MenuOptions = $MasterMenuList | Where-Object { $_ -match $Filter } } else { $MenuOptions = $MasterMenuList } For ($i = 0; $i -le $MaxValue; $i++) { If ($i -eq $Selection) { Write-Host -BackgroundColor Cyan -ForegroundColor Black "[ $($MenuOptions[$i]) ]" } Else { Write-Host " $($MenuOptions[$i]) " } } $KeyInput = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown").virtualkeycode Switch ($KeyInput) { 13 { $EnterPressed = $True $Selection = $MasterMenuList.IndexOf($MenuOptions[$selection]) Return $Selection if (!$VerbosePreference -eq "Continue") { Clear-Host } break } 38 { If ($Selection -eq 0) { $Selection = $MaxValue } Else { $Selection -= 1 } if (!$VerbosePreference -eq "Continue") { Clear-Host } break } 40 { If ($Selection -eq $MaxValue) { $Selection = 0 } Else { $Selection += 1 } if (!$VerbosePreference -eq "Continue") { Clear-Host } break } 8 { if ($Filter.Length -gt 0) { $Filter = $Filter.Substring(0, $Filter.Length - 1) if (!$VerbosePreference -eq "Continue") { Clear-Host } } else { $Filter = "" if (!$VerbosePreference -eq "Continue") { Clear-Host } } } Default { $Filter += [char]$KeyInput if (!$VerbosePreference -eq "Continue") { Clear-Host } } } } } function Update-ProjectFile() { $projectFileMaster = Get-Content (Join-Path (Get-Module -Name microsoft.powerplatform.devops).Path -ChildPath ..\Private\emptyProject.json) | ConvertFrom-Json if ($projectFileMaster) { $properties = Get-Member -InputObject $global:devops_projectFile -MemberType Properties $properties | ForEach-Object { $hasProperty = (Get-Member -InputObject $projectFileMaster.$($_.Name) -MemberType Properties -ErrorAction SilentlyContinue) if ($hasProperty) { $projectFileMaster.$($_.Name) = $global:devops_projectFile.$($_.Name) } } $global:devops_projectFile = $projectFileMaster $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") } } function Get-AzureAccounts { try { $sel = $null $AZAccounts = az account list | ConvertFrom-Json [array]$options = "Login to a New Account" $options += $AZAccounts | ForEach-Object { "$($_.name) $($_.user.name)" } do { $sel = Invoke-Menu -MenuTitle "---- Please Select your Subscription ------" -MenuOptions $options } until ($sel -ge 0) if ($sel -eq 0) { if ($global:devops_isDocker) { az login --use-device-code --allow-no-subscriptions } else { az login --allow-no-subscriptions } #Get Azure Accounts / Subscriptions $AZAccounts = az account list | ConvertFrom-Json Get-AzureAccounts } else { $global:devops_selectedSubscription = $AZAccounts[$sel - 1].id $global:devops_selectedSubscriptionName = $AZAccounts[$sel - 1].name az account set --subscription $global:devops_selectedSubscription } } catch { Write-Error $_ pause } } function Get-AzureLogins() { [array]$options = "Login to a New Account" $AZCreds = az account list --query '[].{Name:user.name, Type:user.type, ID:id, Subscription:name}' --output json | ConvertFrom-Json $UniqueCreds = $AZCreds | Sort-Object -Property * -Unique $options += $UniqueCreds | ForEach-Object { "$($_.Name) ($($_.Type))($($_.Subscription))" } do { $sel = Invoke-Menu -MenuTitle "---- Please Select your AAD Subscription ------" -MenuOptions $options } until ($sel -ge 0) if ($sel -eq 0) { if ($global:devops_isDocker) { az login --use-device-code --allow-no-subscriptions } else { az login --allow-no-subscriptions } Get-AzureLogins } else { $global:devops_DataverseEmail = $UniqueCreds[$sel - 1].Name $global:devops_DataverseADSubscription = $UniqueCreds[$sel - 1].id $global:devops_DataverseCredType = $UniqueCreds[$sel - 1].Type $global:devops_HasDataverseLogin = $true } } function Get-DataverseLogin() { if (!$global:devops_HasDataverseLogin) { $message = "Connecting to Dataverse Environment" Write-Host $message Write-Host "" [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 if ($global:devops_ClientID) { $global:devops_DataverseCredType = "servicePrincipal" } else { Get-AzureLogins } if ($global:devops_DataverseCredType -eq "user") { [String]$userString = $global:devops_DataverseEmail Authenticate-PowerPlatform($userString,"https://management.azure.com/") Authenticate-PowerPlatform($userString,"https://service.powerapps.com/") } else { Write-Host "Using Service Principal" if ($global:devops_projectFile.ClientSecretAKVName) { az account set --subscription $global:devops_projectFile.selectedSubscription $kvretrieve = az keyvault secret show --name $global:devops_projectFile.ClientSecretAKVName --vault-name $global:devops_projectFile.AzureKeyVaultName | ConvertFrom-Json $global:clientSecret = $kvretrieve.value } else { $clientKeySS = ($global:devops_configFile.Projects[$global:devops_projectConfigID].ClientSecret) | ConvertTo-SecureString $global:clientSecret = (New-Object PSCredential "user", $clientKeySS).GetNetworkCredential().Password } try { Add-PowerAppsAccount -ApplicationId $global:devops_ClientID -ClientSecret $global:clientSecret -TenantID $global:devops_TenantID $ManagementApps = Get-PowerAppManagementApps $SPAdminPortalAccess = $ManagementApps.value | Where-Object applicationId -eq $global:devops_ClientID $SPAdminPortalAccess = $SPAdminPortalAccess.applicationId if (!$SPAdminPortalAccess) { $EnableAccess = Read-Host -Prompt "Service Principal $global:devops_ClientID does not have permissions to administer Power Platform, would you like to enable access ? [y/n]" if ($EnableAccess.ToLower() -eq 'y') { Write-Host "Please Login to Power Platform with a User who has access to Administer Environments" $global:currentSession.loggedIn = $false $tempClientID = $global:devops_ClientID $global:devops_ClientID = $null Get-DataverseLogin $global:devops_ClientID = $tempClientID $global:devops_DataverseCredType = "servicePrincipal" $SPAdminPortalAccess = New-PowerAppManagementApp -ApplicationId $global:devops_ClientID if ($SPAdminPortalAccess.StatusCode -eq "403") { Throw $SPAdminPortalAccess } $global:currentSession.loggedIn = $false Add-PowerAppsAccount -ApplicationId $global:devops_ClientID -ClientSecret $global:clientSecret -TenantID $global:devops_TenantID $global:devops_HasDataverseLogin = $true } else { Throw $SPAdminPortalAccess } } else { $global:devops_HasDataverseLogin = $true } } catch { $global:devops_HasDataverseLogin = $false Write-Host $_ pause } } } } function Authenticate-PowerPlatform() { [CmdletBinding()] Param( [Parameter(Mandatory = $True)]$args ) $theUser = $args[0] if ($args[1]) { $theAudience = $args[1] } else { $theAudience = "https://management.azure.com/" } $authBaseUri = switch ($Endpoint) { "usgovhigh" { "https://login.microsoftonline.us" } "dod" { "https://login.microsoftonline.us" } default { "https://login.windows.net" } }; [string]$Endpoint = "prod" [string]$Audience = $theAudience [string]$TenantID = $null [string]$CertificateThumbprint = $null [string]$ClientSecret = $null $azTokenCache = Get-Content $env:USERPROFILE\.azure\accessTokens.json | ConvertFrom-Json $authResult = $azTokenCache | Where-Object resource -EQ $Audience | Where-Object userId -eq $theUser if (!$authResult) { $getToken = az account get-access-token --resource $Audience -s $global:devops_DataVerseADSubscription if (!$getToken) { Write-Host "Re-Authenticating $theUser" if ($global:devops_isDocker) { az login --use-device-code --allow-no-subscriptions } else { az login --allow-no-subscriptions } Authenticate-PowerPlatform($theUser, $Audience) } $azTokenCache = Get-Content $env:USERPROFILE\.azure\accessTokens.json | ConvertFrom-Json $authResult = $azTokenCache | Where-Object resource -EQ $Audience | Where-Object userId -eq $theUser } $claims = Get-JwtTokenClaimsForPA -JwtToken $authResult.AccessToken $global:currentSession = @{ loggedIn = $true; idToken = $authResult.IdToken; upn = $claims.upn; tenantId = $claims.tid; userId = $claims.oid; applicationId = $claims.appid; certificateThumbprint = $CertificateThumbprint; clientSecret = $ClientSecret; secureClientSecret = $SecureClientSecret; refreshToken = $authResult.RefreshToken; expiresOn = (Get-Date).AddHours(8); resourceTokens = @{ $Audience = @{ accessToken = $authResult.AccessToken; expiresOn = $authResult.ExpiresOn; } }; selectedEnvironment = "~default"; authBaseUri = $authBaseUri; flowEndpoint = switch ($Endpoint) { "prod" { "api.flow.microsoft.com" } "usgov" { "gov.api.flow.microsoft.us" } "usgovhigh" { "high.api.flow.microsoft.us" } "dod" { "api.flow.appsplatform.us" } "china" { "api.powerautomate.cn" } "preview" { "preview.api.flow.microsoft.com" } "tip1" { "tip1.api.flow.microsoft.com" } "tip2" { "tip2.api.flow.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'" } }; powerAppsEndpoint = switch ($Endpoint) { "prod" { "api.powerapps.com" } "usgov" { "gov.api.powerapps.us" } "usgovhigh" { "high.api.powerapps.us" } "dod" { "api.apps.appsplatform.us" } "china" { "api.powerapps.cn" } "preview" { "preview.api.powerapps.com" } "tip1" { "tip1.api.powerapps.com" } "tip2" { "tip2.api.powerapps.com" } default { throw "Unsupported endpoint '$Endpoint'" } }; bapEndpoint = switch ($Endpoint) { "prod" { "api.bap.microsoft.com" } "usgov" { "gov.api.bap.microsoft.us" } "usgovhigh" { "high.api.bap.microsoft.us" } "dod" { "api.bap.appsplatform.us" } "china" { "api.bap.partner.microsoftonline.cn" } "preview" { "preview.api.bap.microsoft.com" } "tip1" { "tip1.api.bap.microsoft.com" } "tip2" { "tip2.api.bap.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'" } }; graphEndpoint = switch ($Endpoint) { "prod" { "graph.windows.net" } "usgov" { "graph.windows.net" } "usgovhigh" { "graph.windows.net" } "dod" { "graph.windows.net" } "china" { "graph.windows.net" } "preview" { "graph.windows.net" } "tip1" { "graph.windows.net" } "tip2" { "graph.windows.net" } default { throw "Unsupported endpoint '$Endpoint'" } }; cdsOneEndpoint = switch ($Endpoint) { "prod" { "api.cds.microsoft.com" } "usgov" { "gov.api.cds.microsoft.us" } "usgovhigh" { "high.api.cds.microsoft.us" } "dod" { "dod.gov.api.cds.microsoft.us" } "preview" { "preview.api.cds.microsoft.com" } "tip1" { "tip1.api.cds.microsoft.com" } "tip2" { "tip2.api.cds.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'" } }; }; } function Get-JwtTokenClaimsForPA { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$JwtToken ) $tokenSplit = $JwtToken.Split(".") $claimsSegment = $tokenSplit[1].Replace(" ", "+").Replace("-", "+"); $mod = $claimsSegment.Length % 4 if ($mod -gt 0) { $paddingCount = 4 - $mod; for ($i = 0; $i -lt $paddingCount; $i++) { $claimsSegment += "=" } } $decodedClaimsSegment = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($claimsSegment)) return ConvertFrom-Json $decodedClaimsSegment } function Format-Json { <# .SYNOPSIS Prettifies JSON output. .DESCRIPTION Reformats a JSON string so the output looks better than what ConvertTo-Json outputs. .PARAMETER Json Required: [string] The JSON text to prettify. .PARAMETER Minify Optional: Returns the json string compressed. .PARAMETER Indentation Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4. .PARAMETER AsArray Optional: If set, the output will be in the form of a string array, otherwise a single string is output. .EXAMPLE $json | ConvertTo-Json | Format-Json -Indentation 2 #> [CmdletBinding(DefaultParameterSetName = 'Prettify')] Param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string]$Json, [Parameter(ParameterSetName = 'Minify')] [switch]$Minify, [Parameter(ParameterSetName = 'Prettify')] [ValidateRange(1, 1024)] [int]$Indentation = 4, [Parameter(ParameterSetName = 'Prettify')] [switch]$AsArray ) if ($PSCmdlet.ParameterSetName -eq 'Minify') { return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress } # If the input JSON text has been created with ConvertTo-Json -Compress # then we first need to reconvert it without compression if ($Json -notmatch '\r?\n') { $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 } $indent = 0 $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)' $result = $Json -split '\r?\n' | ForEach-Object { # If the line contains a ] or } character, # we need to decrement the indentation level unless it is inside quotes. if ($_ -match "[}\]]$regexUnlessQuoted") { $indent = [Math]::Max($indent - $Indentation, 0) } # Replace all colon-space combinations by ": " unless it is inside quotes. $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ') # If the line contains a [ or { character, # we need to increment the indentation level unless it is inside quotes. if ($_ -match "[\{\[]$regexUnlessQuoted") { $indent += $Indentation } $line } if ($AsArray) { return $result } return $result -Join [Environment]::NewLine } function Invoke-OpenSolution { . "$global:devops_projectLocation\$global:devops_gitRepo.sln" } function Get-DataverseConnection { Param( [string] [Parameter(Mandatory = $true)] $DeployServerUrl ) try { #Install-XrmModule Get-DataverseLogin if ($global:devops_DataverseCredType -eq "user") { try { if ($global:devops_isDocker) { Write-Host "Warning: It is recommended you configure and use a Service Principal instead of Username and Password (to prevent MFA related issues)" -ForegroundColor Yellow Write-Host "" $SecurePassword = Read-Host "Enter Password for $global:devops_DataverseEmail" -AsSecureString $global:Password = (New-Object PSCredential "user", $SecurePassword).GetNetworkCredential().Password [string]$CrmConnectionString = "AuthType=OAuth;Username=$global:devops_DataverseEmail;Password=$global:Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;TokenCacheStorePath=$env:APPDATA\Microsoft.PowerPlatform.DevOps\dataverse_cache.data;LoginPrompt=Never" } else { [string]$CrmConnectionString = "AuthType=OAuth;Username=$global:devops_DataverseEmail;Password=$global:Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;TokenCacheStorePath=$env:APPDATA\Microsoft.PowerPlatform.DevOps\dataverse_cache.data;LoginPrompt=Auto" } $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600 return $conn } catch { Write-Host $_ pause } } elseif ($global:devops_DataverseCredType -eq "servicePrincipal") { try { [string]$CrmConnectionString = "AuthType=ClientSecret;Url=$DeployServerUrl;ClientId=$global:devops_ClientID;ClientSecret=$global:clientSecret" $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600 if (!$conn.IsReady -and $conn.LastCrmError.Contains("Invalid Login")) { $AddSPAsSysAdmin = Read-Host -Prompt "The Service Principal $global:devops_ClientID does not have access to $DeployServerUrl, would you like to add access now ? [y/n]" if ($AddSPAsSysAdmin.ToLower() -eq "y") { try { $global:currentSession.loggedIn = $false Add-D365ApplicationUser -d365ResourceName $DeployServerUrl -servicePrincipal $global:devops_ClientID -roleNames "System Administrator" } catch { Write-Host $_ pause } $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600 } } return $conn } catch { Write-Host $_ pause } } else { try { $conn = Connect-CrmOnline -ServerUrl $DeployServerUrl -ConnectionTimeoutInSeconds 600 -ForceOAuth return $conn } catch { Write-Host $_ pause } } } catch { Write-Host $_ pause } } function Get-ConfigJSON($StartPath) { $global:devops_BaseConfig = Join-Path $StartPath -ChildPath "$SelectedSolution\Scripts\config.json" # Load and parse the JSON configuration file try { $global:devops_Config = Get-Content -Path $global:devops_BaseConfig -Raw -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue | ConvertFrom-Json -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue } catch { Write-Error "The Base configuration file is missing!" -Stop } # Check the configuration if (!($global:devops_Config)) { Write-Error "The Base configuration file is missing!" -Stop } $global:devops_ServerUrl = ($global:devops_Config.target.ServerUrl) $global:devops_SolutionName = ($global:devops_Config.target.SolutionName) Write-Host $global:devops_ServerUrl Write-Host $global:devops_SolutionName } # # _Helpers.ps1 # function Get-DeployEnvironments { try { $Environments = Get-Content -Path $global:devops_projectLocation\Environments.json | ConvertFrom-Json [array]$options = "[Go Back]" $options += $Environments | ForEach-Object { "$($_.EnvironmentName)" } do { $sel = Invoke-Menu -MenuTitle "---- Select Environment to Deploy To ------" -MenuOptions $options } until ($sel -ge 0) if ($sel -eq 0) { return } else { if ($global:devops_DataverseCredType -eq "servicePrincipal") { Write-Host "Using Service Principal" Start-DeploySolution -DeployServerUrl $Environments[$sel - 1].EnvironmentURL -UserName $global:devops_ClientID -Password $global:clientSecret -PipelinePath $global:devops_projectLocation -UseClientSecret $true -EnvironmentName $Environments[$sel - 1].EnvironmentName -RunLocally $true } else { Write-Host "Using User Credentials" Start-DeploySolution -DeployServerUrl $Environments[$sel - 1].EnvironmentURL -UserName $global:devops_DataverseEmail -Password "" -PipelinePath $global:devops_projectLocation -UseClientSecret $false -EnvironmentName $Environments[$sel - 1].EnvironmentName -RunLocally $true } pause } } catch { Write-Error $_ pause } } |