src/profile.psm1
Set-StrictMode -Version Latest # Load common code $here = Split-Path -Parent $MyInvocation.MyCommand.Path . "$here\common.ps1" $profilesPath = "$HOME/vsteam_profiles.json" function Get-VSTeamProfile { [CmdletBinding()] param( # Name is an array so I can pass an array after -Name # I can also use pipe [parameter(Mandatory = $false, Position = 1, ValueFromPipelineByPropertyName = $true)] [string] $Name ) process { if (Test-Path $profilesPath) { try { # We needed to add ForEach-Object to unroll and show the inner type $result = Get-Content $profilesPath | ConvertFrom-Json # convert old URL in profile to new URL if ($result) { $result | ForEach-Object { if ($_.URL -match "(?<protocol>https?\://)?(?<account>[A-Z0-9][-A-Z0-9]*[A-Z0-9])(?<domain>\.visualstudio\.com)") { $_.URL = "https://dev.azure.com/$($matches.account)" } } } if ($Name) { $result = $result | Where-Object Name -eq $Name } if ($result) { $result | ForEach-Object { # Setting the type lets me format it $_.PSObject.TypeNames.Insert(0, 'Team.Profile') if ($_.PSObject.Properties.Match('Token').count -eq 0) { # This is a profile that was created before the module supported # bearer tokens. The rest of the module expects there to be a Token # property. Add one with a value of '' $_ | Add-Member NoteProperty 'Token' '' } $_ } } } catch { # Catch any error and fail to the return empty array below Write-Error "Error ready Profiles file. Use Add-VSTeamProfile to generate new file." } } } } function Remove-VSTeamProfile { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low")] param( # Name is an array so I can pass an array after -Name # I can also use pipe [parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] [string[]] $Name, [switch] $Force ) begin { $profiles = Get-VSTeamProfile } process { foreach ($item in $Name) { if ($Force -or $pscmdlet.ShouldProcess($item, "Remove-VSTeamProfile")) { # See if this item is already in there $profiles = $profiles | Where-Object Name -ne $item } } } end { $contents = ConvertTo-Json $profiles if ([string]::isnullorempty($contents)) { $contents = '' } Set-Content -Path $profilesPath -Value $contents } } function Add-VSTeamProfile { [CmdletBinding(DefaultParameterSetName = 'Secure')] param( [parameter(ParameterSetName = 'Windows', Mandatory = $true, Position = 1)] [parameter(ParameterSetName = 'Secure', Mandatory = $true, Position = 1)] [Parameter(ParameterSetName = 'Plain')] [string] $Account, [parameter(ParameterSetName = 'Plain', Mandatory = $true, Position = 2, HelpMessage = 'Personal Access Token')] [string] $PersonalAccessToken, [parameter(ParameterSetName = 'Secure', Mandatory = $true, HelpMessage = 'Personal Access Token')] [securestring] $SecurePersonalAccessToken, [string] $Name, [ValidateSet('TFS2017', 'TFS2018', 'VSTS')] [string] $Version, [switch] $UseBearerToken ) DynamicParam { # Only add these options on Windows Machines if (_isOnWindows) { # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParameterName2 = 'UseWindowsAuthentication' # Create the collection of attributes $AttributeCollection2 = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute2 = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute2.Mandatory = $true $ParameterAttribute2.ParameterSetName = "Windows" $ParameterAttribute2.HelpMessage = "On Windows machines allows you to use the active user identity for authentication. Not available on other platforms." # Add the attributes to the attributes collection $AttributeCollection2.Add($ParameterAttribute2) # Create and return the dynamic parameter $RuntimeParameter2 = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName2, [switch], $AttributeCollection2) $RuntimeParameterDictionary.Add($ParameterName2, $RuntimeParameter2) return $RuntimeParameterDictionary } } process { if ($SecurePersonalAccessToken) { # Even when promoted for a Secure Personal Access Token you can just # press enter. This leads to an empty PAT so error below. # Convert the securestring to a normal string # this was the one technique that worked on Mac, Linux and Windows $_pat = _convertSecureStringTo_PlainText -SecureString $SecurePersonalAccessToken } else { $_pat = $PersonalAccessToken } if (_isOnWindows) { # Bind the parameter to a friendly variable $UsingWindowsAuth = $PSBoundParameters[$ParameterName2] if (!($_pat) -and !($UsingWindowsAuth)) { Write-Error 'Personal Access Token must be provided if you are not using Windows Authentication; please see the help.' return } } # If they only gave an account name add https://dev.azure.com if ($Account -notlike "*/*") { if (-not $Name) { $Name = $Account } $Account = "https://dev.azure.com/$($Account)" } # If they gave https://dev.azure.com extract Account and Profile name if ($Account -match "(?<protocol>https\://)?(?<domain>dev\.azure\.com/)(?<account>[A-Z0-9][-A-Z0-9]*[A-Z0-9])") { if (-not $Name) { $Name = $matches.account } $Account = "https://dev.azure.com/$($matches.account)" } # If they gave https://xxx.visualstudio.com extract Account and Profile name, convert to new URL if ($Account -match "(?<protocol>https?\://)?(?<account>[A-Z0-9][-A-Z0-9]*[A-Z0-9])(?<domain>\.visualstudio\.com)") { if (-not $Name) { $Name = $matches.account } $Account = "https://dev.azure.com/$($matches.account)" } if ($UseBearerToken.IsPresent) { $authType = 'Bearer' $token = $_pat $encodedPat = '' } else { $token = '' $authType = 'Pat' $encodedPat = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$_pat")) } # If no SecurePersonalAccessToken is entered, and on windows, are we using default credentials for REST calls if ((!$_pat) -and (_isOnWindows) -and ($UsingWindowsAuth)) { Write-Verbose 'Using Default Windows Credentials for authentication; no Personal Access Token required' $encodedPat = '' $token = '' $authType = 'OnPremise' } if (-not $Name) { $Name = $Account } # See if this item is already in there # I am testing URL because the user may provide a different # name and I don't want two with the same URL. # The @() forces even 1 item to an array $profiles = @(Get-VSTeamProfile | Where-Object URL -ne $Account) $newProfile = [PSCustomObject]@{ Name = $Name URL = $Account Type = $authType Pat = $encodedPat Token = $token Version = (_getVSTeamAPIVersion -Instance $Account -Version $Version) } $profiles += $newProfile $contents = ConvertTo-Json $profiles Set-Content -Path $profilesPath -Value $contents } } function Update-VSTeamProfile { [CmdletBinding(DefaultParameterSetName = 'Secure', SupportsShouldProcess = $true, ConfirmImpact = "Medium")] param( [parameter(ParameterSetName = 'Plain', Mandatory = $true, HelpMessage = 'Personal Access Token')] [string] $PersonalAccessToken, [parameter(ParameterSetName = 'Secure', Mandatory = $true, HelpMessage = 'Personal Access Token')] [securestring] $SecurePersonalAccessToken, [switch] $Force ) DynamicParam { # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $profileArrSet = Get-VSTeamProfile | Select-Object -ExpandProperty Name if ($profileArrSet) { $profileParam = _buildDynamicParam -ParameterName 'Name' -Mandatory $true -Position 0 -arrSet $profileArrSet } else { $profileParam = _buildDynamicParam -ParameterName 'Name' -Mandatory $true -Position 0 } $RuntimeParameterDictionary.Add('Name', $profileParam) return $RuntimeParameterDictionary } process { $Name = $PSBoundParameters['Name'] if ($Force -or $pscmdlet.ShouldProcess($Name, "Update-VSTeamProfile")) { if ($SecurePersonalAccessToken) { # Even when promoted for a Secure Personal Access Token you can just # press enter. This leads to an empty PAT so error below. # Convert the securestring to a normal string # this was the one technique that worked on Mac, Linux and Windows $_pat = _convertSecureStringTo_PlainText -SecureString $SecurePersonalAccessToken } else { $_pat = $PersonalAccessToken } $token = '' $authType = 'Pat' $encodedPat = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$_pat")) $profile = Get-VSTeamProfile | Where-Object Name -eq $Name if ($profile) { # See if this item is already in there # I am testing URL because the user may provide a different # name and I don't want two with the same URL. # The @() forces even 1 item to an array # Now get an array of all profiles but this one and update this one. $profiles = @(Get-VSTeamProfile | Where-Object Name -ne $Name) $newProfile = [PSCustomObject]@{ Name = $Name URL = $profile.URL Type = $authType Pat = $encodedPat Token = $token Version = $profile.Version } $profiles += $newProfile $contents = ConvertTo-Json $profiles Set-Content -Path $profilesPath -Value $contents } else { # This will happen when they don't have any profiles. Write-Warning 'Profile not found' } } } } Set-Alias Get-Profile Get-VSTeamProfile Set-Alias Add-Profile Add-VSTeamProfile Set-Alias Remove-Profile Remove-VSTeamProfile Set-Alias Update-Profile Update-VSTeamProfile Export-ModuleMember ` -Function Get-VSTeamProfile, Add-VSTeamProfile, Remove-VSTeamProfile, Update-VSTeamProfile ` -Alias Get-Profile, Add-Profile, Remove-Profile, Update-Profile |