Skip to content

Commit

Permalink
using the export approach
Browse files Browse the repository at this point in the history
  • Loading branch information
praval-microsoft committed Dec 14, 2024
1 parent e575715 commit 3552b97
Show file tree
Hide file tree
Showing 16 changed files with 613 additions and 213 deletions.
105 changes: 64 additions & 41 deletions Tasks/PowerShellV2/AccessTokenHelper.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function Global:Get-VstsFederatedTokenPSV2Task {
Import-Module Microsoft.PowerShell.Security
Import-Module $PSScriptRoot\ps_modules\VstsTaskSdk

function Global:Get-VstsFederatedTokenPS2Task {
param(
[Parameter(Mandatory=$true)]
[string]$serviceConnectionId,
Expand All @@ -13,7 +16,8 @@ function Global:Get-VstsFederatedTokenPSV2Task {
$projectId = Get-VstsTaskVariable -Name 'System.TeamProjectId' -Require

$url = $uri + "$projectId/_apis/distributedtask/hubs/$hub/plans/$planId/jobs/$jobId/oidctoken?serviceConnectionId=$serviceConnectionId&api-version=7.1-preview.1"

$env:praval = $env:praval + "`n" + $url

$headers = @{
"Authorization" = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($vstsAccessToken)"))
"Content-Type" = "application/json"
Expand All @@ -26,58 +30,76 @@ function Global:Get-VstsFederatedTokenPSV2Task {
$responseContent = $response.Content | ConvertFrom-Json
$oidcToken = $responseContent.oidcToken


if ($null -eq $oidcToken -or $oidcToken -eq [string]::Empty) {
Write-Verbose "Failed to create OIDC token."
$env:praval = $env:praval + "`n" + "Failed to create OIDC token."
throw (New-Object System.Exception("CouldNotGenerateOidcToken"))
}

Write-Verbose "OIDC Token generated Successfully"
$env:praval = $env:praval + "`n" + "OIDC Token generated Successfully"
return $oidcToken
}
New-Alias -Name 'Get-VstsFederatedToken' -Value 'Global:Get-VstsFederatedTokenPSV2Task' -Scope Global
New-Alias -Name 'Get-VstsFederatedToken' -Value 'Global:Get-VstsFederatedTokenPS2Task' -Scope Global

function Global:Get-WiscAccessTokenPSV2Task {
param(
[Parameter(Mandatory=$true)]
[string]$connectedServiceName
)
$vstsEndpoint = Get-VstsEndpoint -Name $connectedServiceName -Require
$taskDict
)

$clientId = $taskDict["ClientId"]
$envAuthUrl = $taskDict["EnvAuthUrl"]
$tenantId = $taskDict["TenantId"]
$connectedServiceName = $taskDict["ConnectedServiceName"]
$vstsAccessToken = $taskDict["VstsAccessToken"]

Add-Type -Path "$PSScriptRoot\ps_modules\VstsAzureRestHelpers_\msal\Microsoft.Identity.Client.dll"
$clientBuilder = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($clientId).WithAuthority($envAuthUrl, $tenantId)

$oidc_token = Get-VstsFederatedToken -serviceConnectionId $connectedServiceName -vstsAccessToken $vstsAccessToken
$env:praval = $env:praval + "`n" + "oidc_token $oidc_token"
$msalClientInstance = $clientBuilder.WithClientAssertion($oidc_token).Build()

$result = Get-AccessTokenMSALWithCustomScope -endpoint $vstsEndpoint `
-connectedServiceNameARM $connectedServiceName `
-scope "499b84ac-1321-427f-aa17-267ca6975798"
$scope = "499b84ac-1321-427f-aa17-267ca6975798"
[string] $resourceId = $scope + "/.default"
$scopes = [Collections.Generic.List[string]]@($resourceId)

$env:praval = $env:praval + "`n" + "Fetching Access Token - MSAL"
$tokenResult = $msalClientInstance.AcquireTokenForClient($scopes).ExecuteAsync().GetAwaiter().GetResult()

$token = $null
$expirationTime = $null
if($result) {
$token = $result.AccessToken
$expirationTime = $([math]::Round(([DateTime]::Parse($result.ExpiresOn) - [DateTime]::Now).TotalMinutes))
if($tokenResult) {
$token = $tokenResult.AccessToken
$expirationTime = $([math]::Round(([DateTime]::Parse($tokenResult.ExpiresOn) - [DateTime]::Now).TotalMinutes))
}

if ($null -eq $token -or $token -eq [string]::Empty) {
Write-Verbose "Generated token found to be null, returning the System Access Token"
$env:praval = $env:praval + "`n" + "Generated token found to be null, returning the System Access Token"
$token = $env:SystemAccessTokenPowershellV2
} else {
Write-Verbose "Successfully generated the Azure Access token for Service Connection : $connectedServiceName"
$env:praval = $env:praval + "`n" + "Successfully generated the Azure Access token for Service Connection : $connectedServiceName"
if($expirationTime) {
Write-Host "Generated access token with expiration time of $expirationTime minutes."
$env:praval = $env:praval + "`n" + "Generated access token with expiration time of $expirationTime minutes."
}
}

return $token
}
New-Alias -Name 'Get-WiscAccessTokenPSV2Task' -Value 'Global:Get-WiscAccessTokenPSV2Task' -Scope Global

class TokenHandler {
handle($s)
{
$array = $s -split '::'
Write-Host $array
[string]$filePath = $array[0]
[string]$signalFromUserScript = $array[1]
[string]$signalFromTask = $array[2]
[string]$exitSignal = $array[3]
$tokenHandler = [PSCustomObject]@{

TokenHandler = {
param(
[Parameter(Mandatory=$true)]
[string]$filePath,
[Parameter(Mandatory=$true)]
[string]$signalFromUserScript,
[Parameter(Mandatory=$true)]
[string]$signalFromTask,
[Parameter(Mandatory=$true)]
[string]$exitSignal,
$taskDict
)

$eventFromUserScript = $null
$eventFromTask = $null
Expand All @@ -104,12 +126,14 @@ class TokenHandler {
# Apply the ACL to the file
Set-Acl -Path $filePath -AclObject $acl
} else {
Write-Error "Token File not found"
$env:praval = $env:praval + "`n" + "Token File not found"
throw "Token File not found"
}

Write-Verbose "Task: Waiting for signals..."

Write-Host "1-Task: Waiting for signals..."
Write-Verbose "2-Task: Waiting for signals..."
$env:praval = $env:praval + "`n" + "Task: Waiting for signals..."
$env:StartTask = "true"
# Infinite loop to wait for signals and respond
while ($true) {
try {
Expand All @@ -119,42 +143,41 @@ class TokenHandler {
if ($index -eq 0) {

# Signal from UserScript
$env:SystemAccessTokenPowershellV2 = Get-VstsTaskVariable -Name 'System.AccessToken' -Require
$token = ""
try {
[string]$connectedServiceName = (Get-VstsInput -Name ConnectedServiceName)
[string]$connectedServiceName = $taskDict["ConnectedServiceName"]

if ($null -eq $connectedServiceName -or $connectedServiceName -eq [string]::Empty) {
Write-Verbose "No Service connection was found, returning the System Access Token"
$env:praval = $env:praval + "`n" + "No Service connection was found, returning the System Access Token"
$token = $env:SystemAccessTokenPowershellV2
} else {
$token = Get-WiscAccessTokenPSV2Task -connectedServiceName $connectedServiceName
Write-Verbose "Successfully generated the Azure Access token for Service Connection : $connectedServiceName"
$token = Get-WiscAccessTokenPSV2Task -connectedServiceName $connectedServiceName -taskDict $taskDict
$env:praval = $env:praval + "`n" + "Successfully generated the Azure Access token for Service Connection : $connectedServiceName"
}
}
catch {
Write-Verbose "Failed to generate token with message $_, returning the System Access Token"
$env:praval = $env:praval + "`n" + "Failed to generate token with message $_, returning the System Access Token"
$token = $env:SystemAccessTokenPowershellV2

} finally {
$token | Set-Content -Path $filePath
Write-Verbose "Task: Wrote access token to file"
$env:praval = $env:praval + "`n" + "Task: Wrote access token to file"
}

# Signal UserScript to read the file
$tmp = $eventFromTask.Set()

} elseif ($index -eq 1) {
Write-Host "Exiting the loop"
$env:praval = $env:praval + "`n" + "Exiting the loop"
# Exit signal received
break
}
} catch {
Write-Debug "Error occurred while waiting for signals: $_"
$env:praval = $env:praval + "`n" + "Error occurred while waiting for signals: $_"
}
}
} catch {
Write-Debug "Critical error in Task: $_"
$env:praval = $env:praval + "`n" + "Critical error in Task: $_"
} finally {
try {
if ($null -ne $eventFromUserScript ) { $eventFromUserScript.Dispose() }
Expand Down
41 changes: 41 additions & 0 deletions Tasks/PowerShellV2/Vsts.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[switch]$Require)

$originalErrorActionPreference = $ErrorActionPreference
try {
$ErrorActionPreference = 'Stop'

# Get the URL.
$description = Get-LocString -Key PSLIB_EndpointUrl0 -ArgumentList $Name
$key = "ENDPOINT_URL_$Name"
$url = Get-VaultValue -Description $description -Key $key -Require:$Require

# Get the auth object.
$description = Get-LocString -Key PSLIB_EndpointAuth0 -ArgumentList $Name
$key = "ENDPOINT_AUTH_$Name"
if ($auth = (Get-VaultValue -Description $description -Key $key -Require:$Require)) {
$auth = ConvertFrom-Json -InputObject $auth
}

# Get the data.
$description = "'$Name' service endpoint data"
$key = "ENDPOINT_DATA_$Name"
if ($data = (Get-VaultValue -Description $description -Key $key)) {
$data = ConvertFrom-Json -InputObject $data
}

# Return the endpoint.
if ($url -or $auth -or $data) {
New-Object -TypeName psobject -Property @{
Url = $url
Auth = $auth
Data = $data
}
}
} catch {
$ErrorActionPreference = $originalErrorActionPreference
Write-Error $_
}
110 changes: 94 additions & 16 deletions Tasks/PowerShellV2/powershell.ps1
Original file line number Diff line number Diff line change
@@ -1,19 +1,78 @@
[CmdletBinding()]
param()

Import-Module $PSScriptRoot\ps_modules\VstsTaskSdk
Import-Module $PSScriptRoot\ps_modules\VstsAzureRestHelpers_
Import-Module $PSScriptRoot\ps_modules\Sanitizer
Import-Module Microsoft.PowerShell.Security

Add-Type -AssemblyName "System.Threading"
Add-Type -AssemblyName "System"

. $PSScriptRoot\helpers.ps1

$signalFromUserScript = "Global\SignalFromUserScript" + [System.Guid]::NewGuid().ToString()
$signalFromTask = "Global\SignalFromTask" + [System.Guid]::NewGuid().ToString()
$exitSignal = "Global\ExitSignal" + [System.Guid]::NewGuid().ToString()
$env:praval = ""

$defaultEnvironmentMSALAuthUri = "https://login.microsoftonline.com/"
$defaultEnvironmentADALAuthUri = "https://login.windows.net/"
$azureStack = "AzureStack"

function Get-EnvironmentAuthUrl {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)] $endpoint,
[Parameter(Mandatory = $false)] $useMSAL = $false
)

$envAuthUrl = if ($useMSAL) { $endpoint.Data.activeDirectoryAuthority } else { $endpoint.Data.environmentAuthorityUrl }

if ([string]::IsNullOrEmpty($envAuthUrl)) {
if (($endpoint.Data.Environment) -and ($endpoint.Data.Environment -eq $azureStack)) {
Write-Verbose "MSAL - Get-EnvironmentAuthUrl - azureStack is used"
$endpoint = Add-AzureStackDependencyData -Endpoint $endpoint
$envAuthUrl = $endpoint.Data.environmentAuthorityUrl
}
else {
Write-Verbose "MSAL - Get-EnvironmentAuthUrl - fallback is used"
# fallback
$envAuthUrl = if ($useMSAL) { $defaultEnvironmentMSALAuthUri } else { $defaultEnvironmentADALAuthUri }
}
}

Write-Verbose "MSAL - Get-EnvironmentAuthUrl - endpoint=$endpoint"
Write-Verbose "MSAL - Get-EnvironmentAuthUrl - useMSAL=$useMSAL"
Write-Verbose "MSAL - Get-EnvironmentAuthUrl - envAuthUrl=$envAuthUrl"

return $envAuthUrl
}

function Get-TaskDictionary {
$dictionary = @{}

$connectedServiceName = (Get-VstsInput -Name ConnectedServiceName)
$dictionary["ConnectedServiceName"] = $connectedServiceName
Write-Verbose "connectedServiceName : $connectedServiceName"

if($null -eq $connectedServiceName -or $connectedServiceName -eq [string]::Empty) {
return $dictionary
}

$endpoint = Get-VstsEndpoint -Name $connectedServiceName -Require
$dictionary["ClientId"] = $endpoint.Auth.Parameters.ServicePrincipalId
$dictionary["TenantId"] = $endpoint.Auth.Parameters.TenantId
$dictionary["EnvAuthUrl"] = Get-EnvironmentAuthUrl -endpoint $endpoint -useMSAL $true

$vstsEndpoint = Get-VstsEndpoint -Name SystemVssConnection -Require
$dictionary["VstsAccessToken"] = $vstsEndpoint.auth.parameters.AccessToken

return $dictionary
}

function Get-EnvDictionary {
$envVars = @{}
[System.Environment]::GetEnvironmentVariables().GetEnumerator() | ForEach-Object {
$envVars[$_.Key] = $_.Value
}
return $envVars
}

function Get-ActionPreference {
param (
Expand Down Expand Up @@ -48,21 +107,39 @@ try {
Assert-VstsPath -LiteralPath $tempDirectory -PathType 'Container'
$tokenfilePath = [System.IO.Path]::Combine($tempDirectory, "$([System.Guid]::NewGuid()).txt")

# Create a runspace to handle the Async communication between the Task and User Script for Access Token
$runspacePool = [runspacefactory]::CreateRunspacePool(1, 1)
$runspacePool.Open()

$accessTokenHelperFilePath = "$PSScriptRoot\AccessTokenHelper.ps1"
. $accessTokenHelperFilePath
$taskDict = Get-TaskDictionary
$envVars = Get-EnvDictionary

try {
$ts = New-Object System.Threading.ParameterizedThreadStart([TokenHandler]::new(), [TokenHandler]::new().GetType().GetMethod("handle").MethodHandle.GetFunctionPointer());
$thread = [System.Threading.Thread]::new($ts);
$arg = "$tokenfilePath::$signalFromUserScript::$signalFromTask::$exitSignal"
$thread.Start($arg);
}
catch {
Write-Host "Something failed ; $_"
}
$psRunspace = [powershell]::Create().AddScript({
param($accessTokenHelperFilePath,$filePath, $signalFromUserScript, $signalFromTask, $exitSignal, $taskDict, $envVars)
try {
$envVars.GetEnumerator() | ForEach-Object {
[System.Environment]::SetEnvironmentVariable($_.Key, $_.Value, [System.EnvironmentVariableTarget]::Process)
}

. $accessTokenHelperFilePath
$tokenHandler.TokenHandler.Invoke($filePath, $signalFromUserScript, $signalFromTask, $exitSignal, $taskDict)
return $env:praval
} catch {
$env:praval = $env:praval + " " + $_
return $env:praval
}
}).AddArgument($accessTokenHelperFilePath).AddArgument($tokenfilePath).AddArgument($signalFromUserScript).AddArgument($signalFromTask).AddArgument($exitSignal).AddArgument($taskDict).AddArgument($envVars)

$psRunspace.RunspacePool = $runspacePool
$rsoutput = $psRunspace.BeginInvoke()

while ($env:StartTask -ne "true") {
Start-Sleep 5
Write-Host "Waiting....."
}
# Wait for the async runspace to start and get ready to listen to User scripts requests
Start-Sleep 30
Start-Sleep 5

# Get inputs.
$input_errorActionPreference = Get-ActionPreference -VstsInputName 'errorActionPreference' -DefaultAction 'Stop'
Expand Down Expand Up @@ -332,6 +409,7 @@ finally {
# Signal Task to exit
$eventExit = New-Object System.Threading.EventWaitHandle($false, [System.Threading.EventResetMode]::AutoReset, $exitSignal)
$output = $eventExit.Set()
Write-Host $psRunspace.EndInvoke($rsoutput)
Trace-VstsLeavingInvocation $MyInvocation
} catch {
Write-Host "Full Exception Object: $_"
Expand Down
2 changes: 1 addition & 1 deletion Tasks/PowerShellV2/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"version": {
"Major": 2,
"Minor": 250,
"Patch": 124
"Patch": 168
},
"releaseNotes": "Script task consistency. Added support for macOS and Linux.",
"minimumAgentVersion": "2.115.0",
Expand Down
2 changes: 1 addition & 1 deletion Tasks/PowerShellV2/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"version": {
"Major": 2,
"Minor": 250,
"Patch": 124
"Patch": 168
},
"releaseNotes": "ms-resource:loc.releaseNotes",
"minimumAgentVersion": "2.115.0",
Expand Down
4 changes: 2 additions & 2 deletions _generated/PowerShellV2.versionmap.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Default|2.250.124
Node20-225|2.250.125
Default|2.250.168
Node20-225|2.250.169
Loading

0 comments on commit 3552b97

Please sign in to comment.