diff --git a/.github/workflows/azure-login/action.yml b/.github/workflows/azure-login/action.yml deleted file mode 100644 index 01408fd9..00000000 --- a/.github/workflows/azure-login/action.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Azure Login -description: Logs into Azure using a service principal -inputs: - credentials: - description: Your credentials in JSON format - required: true - -runs: - using: "composite" - steps: - - name: Process Azure credentials - uses: actions/github-script@v7 - env: - AZURE_CREDENTIALS: ${{ inputs.credentials }} - with: - script: | - if (!process.env.AZURE_CREDENTIALS) { - core.setFailed('The AZURE_CREDENTIALS secret is required.') - process.exit(1) - } - - const azureCredentials = JSON.parse(process.env.AZURE_CREDENTIALS) - const {clientId, clientSecret, tenantId, subscriptionId} = azureCredentials - - core.setSecret(clientId) - core.exportVariable('AZURE_CLIENT_ID', clientId) - - core.setSecret(clientSecret) - core.exportVariable('AZURE_CLIENT_SECRET', clientSecret) - - core.setSecret(tenantId) - core.exportVariable('AZURE_TENANT_ID', tenantId) - - core.setSecret(subscriptionId) - core.exportVariable('AZURE_SUBSCRIPTION_ID', subscriptionId) - - - name: Azure Login - shell: bash - run: | - echo "Logging into Azure..." - az login --service-principal -u ${{ env.AZURE_CLIENT_ID }} -p ${{ env.AZURE_CLIENT_SECRET }} --tenant ${{ env.AZURE_TENANT_ID }} - echo "Setting subscription..." - az account set --subscription ${{ env.AZURE_SUBSCRIPTION_ID }} --output none diff --git a/.github/workflows/cleanup-self-hosted-runners.yml b/.github/workflows/cleanup-self-hosted-runners.yml index aa8aff4b..f5ab5482 100644 --- a/.github/workflows/cleanup-self-hosted-runners.yml +++ b/.github/workflows/cleanup-self-hosted-runners.yml @@ -7,12 +7,31 @@ on: - cron: "0 */6 * * *" workflow_dispatch: +permissions: + id-token: write # required for Azure login via OIDC + # The following secrets are required for this workflow to run: -# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource -# group specifically for self-hosted Actions Runners. -# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \ -# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \ -# --sdk-auth +# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource +# group specifically for self-hosted Actions Runners, and to add a federated identity +# to authenticate as the currently-running GitHub workflow. +# az identity create --name -g +# az identity federated-credential create \ +# --identity-name \ +# --resource-group \ +# --name github-workflow \ +# --issuer https://token.actions.githubusercontent.com \ +# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ +# --audiences api://AzureADTokenExchange +# MSYS_NO_PATHCONV=1 \ +# az role assignment create \ +# --assignee \ +# --scope '/subscriptions//resourceGroups/' \ +# --role 'Contributor' +# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which +# the Identity lives) +# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated +# (technically, this is not necessary for `az login --service-principal` with a +# managed identity, but `Azure/login` requires it anyway) # AZURE_RESOURCE_GROUP - Resource group to find the runner(s) in. It's recommended to set up a resource # group specifically for self-hosted Actions Runners. jobs: @@ -22,10 +41,11 @@ jobs: steps: - uses: actions/checkout@v4 - name: Azure Login - uses: ./.github/workflows/azure-login + uses: azure/login@v2 with: - credentials: ${{ secrets.AZURE_CREDENTIALS }} - + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Discover VMs to delete env: GH_APP_ID: ${{ secrets.GH_APP_ID }} diff --git a/.github/workflows/create-azure-self-hosted-runners.yml b/.github/workflows/create-azure-self-hosted-runners.yml index 62bb5539..f9c0e56d 100644 --- a/.github/workflows/create-azure-self-hosted-runners.yml +++ b/.github/workflows/create-azure-self-hosted-runners.yml @@ -20,18 +20,28 @@ on: required: false description: Repo to deploy the runner to. Only needed if runner_scope is set to "repo-level" (defaults to current repository) deallocate_immediately: - type: string + type: choice + options: + - false + - true required: true description: Deallocate the runner immediately after creating it (useful for spinning up runners preemptively) default: "false" + ephemeral: + type: choice + options: + - false + - true + required: true + description: Start the runner in ephemeral mode (i.e. unregister after running one job) + default: "true" env: ACTIONS_RUNNER_SCOPE: ${{ github.event.inputs.runner_scope }} ACTIONS_RUNNER_ORG: "${{ github.event.inputs.runner_org || github.repository_owner }}" ACTIONS_RUNNER_REPO: "${{ github.event.inputs.runner_repo || github.event.repository.name }}" DEALLOCATE_IMMEDIATELY: ${{ github.event.inputs.deallocate_immediately }} - # This has to be a public URL that the VM can access after creation - POST_DEPLOYMENT_SCRIPT_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref }}/azure-self-hosted-runners/post-deployment-script.ps1 + EPHEMERAL_RUNNER: ${{ github.event.inputs.ephemeral }} # Note that you'll need "p" (arm64 processor) and ideally "d" (local temp disk). The number 4 stands for 4 CPU-cores. # For a convenient overview of all arm64 VM types, see e.g. https://azureprice.net/?_cpuArchitecture=Arm64 AZURE_VM_TYPE: Standard_D4plds_v5 @@ -41,15 +51,37 @@ env: AZURE_VM_REGION: westus2 AZURE_VM_IMAGE: win11-24h2-ent +permissions: + id-token: write # required for Azure login via OIDC + contents: read + # The following secrets are required for this workflow to run: -# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource -# group specifically for self-hosted Actions Runners. -# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \ -# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \ -# --sdk-auth +# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource +# group specifically for self-hosted Actions Runners, and to add a federated identity +# to authenticate as the currently-running GitHub workflow. +# az identity create --name -g +# az identity federated-credential create \ +# --identity-name \ +# --resource-group \ +# --name github-workflow \ +# --issuer https://token.actions.githubusercontent.com \ +# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ +# --audiences api://AzureADTokenExchange +# MSYS_NO_PATHCONV=1 \ +# az role assignment create \ +# --assignee \ +# --scope '/subscriptions//resourceGroups/' \ +# --role 'Contributor' +# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which +# the Identity lives) +# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated +# (technically, this is not necessary for `az login --service-principal` with a +# managed identity, but `Azure/login` requires it anyway) # AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in # AZURE_VM_USERNAME - Username of the VM so you can RDP into it # AZURE_VM_PASSWORD - Password of the VM so you can RDP into it +# GH_APP_ID - The ID of the GitHub App whose credentials are to be used to obtain the runner token +# GH_APP_PRIVATE_KEY - The private key of the GitHub App whose credentials are to be used to obtain the runner token jobs: create-runner: runs-on: ubuntu-latest @@ -94,14 +126,21 @@ jobs: # https://github.com/actions/runner/issues/475 - name: Generate Actions Runner token and registration URL run: | + # We need to URL-encode the user name because it usually is a GitHub App, which means that + # it has the suffix `[bot]`. If un-encoded, this would cause a cURL error "bad range in URL" + # because it would mistake this for an IPv6 address or something like that. + user_pwd="$(jq -n \ + --arg user '${{ github.actor }}' \ + --arg pwd '${{ secrets.GITHUB_TOKEN }}' \ + '$user | @uri + ":" + $pwd')" case "$ACTIONS_RUNNER_SCOPE" in "org-level") - ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token" - ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG" + ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token" + ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG" ;; "repo-level") - ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token" - ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO" + ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token" + ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO" ;; *) echo "Unsupported runner scope: $ACTIONS_RUNNER_SCOPE" @@ -127,16 +166,30 @@ jobs: ACTIONS_RUNNER_PATH="D:\a" fi + # Zip up and Base64-encode the post-deployment script; We used to provide a public URL + # for that script instead, but that does not work in private repositories (and we could + # not even use the `GITHUB_TOKEN` to access the file because it lacks the necessary + # scope to read repository contents). + POST_DEPLOYMENT_SCRIPT_ZIP_BASE64="$( + cd azure-self-hosted-runners && + zip -9 tmp.zip post-deployment-script.ps1 >&2 && + base64 -w 0 tmp.zip + )" + + PUBLIC_IP_ADDRESS_NAME1="${{ github.repository_visibility != 'private' && format('{0}-ip', steps.generate-vm-name.outputs.vm_name) || '' }}" + AZURE_ARM_PARAMETERS=$(tr '\n' ' ' <<-END githubActionsRunnerRegistrationUrl="$ACTIONS_RUNNER_REGISTRATION_URL" githubActionsRunnerToken="$ACTIONS_RUNNER_TOKEN" - postDeploymentPsScriptUrl="$POST_DEPLOYMENT_SCRIPT_URL" + postDeploymentScriptZipBase64="$POST_DEPLOYMENT_SCRIPT_ZIP_BASE64" + postDeploymentScriptFileName="post-deployment-script.ps1" virtualMachineImage="$AZURE_VM_IMAGE" virtualMachineName="${{ steps.generate-vm-name.outputs.vm_name }}" virtualMachineSize="$AZURE_VM_TYPE" - publicIpAddressName1="${{ steps.generate-vm-name.outputs.vm_name }}-ip" + publicIpAddressName1="$PUBLIC_IP_ADDRESS_NAME1" adminUsername="${{ secrets.AZURE_VM_USERNAME }}" adminPassword="${{ secrets.AZURE_VM_PASSWORD }}" + ephemeral="$EPHEMERAL_RUNNER" stopService="$DEALLOCATE_IMMEDIATELY" githubActionsRunnerPath="$ACTIONS_RUNNER_PATH" location="$AZURE_VM_REGION" @@ -146,10 +199,12 @@ jobs: echo "AZURE_ARM_PARAMETERS=$AZURE_ARM_PARAMETERS" >> $GITHUB_ENV - name: Azure Login - uses: ./.github/workflows/azure-login + uses: azure/login@v2 with: - credentials: ${{ secrets.AZURE_CREDENTIALS }} - + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - uses: azure/arm-deploy@v2 id: deploy-arm-template with: @@ -179,7 +234,7 @@ jobs: if: always() env: CUSTOM_SCRIPT_OUTPUT: ${{ steps.deploy-arm-template.outputs.customScriptInstanceView }} - run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message' + run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message' | sed 's/${{ secrets.GITHUB_TOKEN }}/***/g' - name: Deallocate the VM for later use if: env.DEALLOCATE_IMMEDIATELY == 'true' diff --git a/.github/workflows/delete-self-hosted-runner.yml b/.github/workflows/delete-self-hosted-runner.yml index e1878a84..bce1441a 100644 --- a/.github/workflows/delete-self-hosted-runner.yml +++ b/.github/workflows/delete-self-hosted-runner.yml @@ -12,13 +12,33 @@ on: env: ACTIONS_RUNNER_NAME: ${{ github.event.inputs.runner_name }} +permissions: + id-token: write # required for Azure login via OIDC + # The following secrets are required for this workflow to run: -# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource -# group specifically for self-hosted Actions Runners. -# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \ -# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \ -# --sdk-auth -# AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in +# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource +# group specifically for self-hosted Actions Runners, and to add a federated identity +# to authenticate as the currently-running GitHub workflow. +# az identity create --name -g +# az identity federated-credential create \ +# --identity-name \ +# --resource-group \ +# --name github-workflow \ +# --issuer https://token.actions.githubusercontent.com \ +# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ +# --audiences api://AzureADTokenExchange +# MSYS_NO_PATHCONV=1 \ +# az role assignment create \ +# --assignee \ +# --scope '/subscriptions//resourceGroups/' \ +# --role 'Contributor' +# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which +# the Identity lives) +# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated +# (technically, this is not necessary for `az login --service-principal` with a +# managed identity, but `Azure/login` requires it anyway) +# AZURE_RESOURCE_GROUP - Resource group to find the runner in. It's recommended to set up a resource +# group specifically for self-hosted Actions Runners. jobs: delete-runner: runs-on: ubuntu-latest @@ -26,7 +46,9 @@ jobs: - name: Azure Login uses: azure/login@v2 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Delete VM '${{ env.ACTIONS_RUNNER_NAME }}' uses: azure/CLI@v2 with: diff --git a/azure-self-hosted-runners/azure-arm-template.json b/azure-self-hosted-runners/azure-arm-template.json index 1bf6b140..4fa0727c 100644 --- a/azure-self-hosted-runners/azure-arm-template.json +++ b/azure-self-hosted-runners/azure-arm-template.json @@ -1,5 +1,5 @@ { - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "githubActionsRunnerRegistrationUrl": { @@ -22,11 +22,18 @@ "description": "Path to the Actions Runner. Keep this path short to prevent Long Path issues, e.g. D:\\a" } }, - "postDeploymentPsScriptUrl": { + "postDeploymentScriptZipBase64": { "type": "string", "minLength": 6, "metadata": { - "description": "URL to the post-deployment PowerShell script. E.g. https://raw.githubusercontent.com/git-for-windows/git-for-windows-automation/main/azure-self-hosted-runners/post-deployment-script.ps1" + "description": "Base64-encoded .zip file containing the post-deployment script" + } + }, + "postDeploymentScriptFileName": { + "type": "string", + "minLength": 6, + "metadata": { + "description": "File name of the post-deployment script" } }, "computerName": { @@ -36,6 +43,12 @@ "description": "Windows Computer Name. Can be maximum 15 characters." } }, + "ephemeral": { + "type": "string", + "metadata": { + "description": "(optional) Whether to spin up an ephemeral runner or not." + } + }, "stopService": { "type": "string", "metadata": { @@ -112,11 +125,14 @@ "vnetName": "[concat(parameters('virtualMachineName'), '-vnet')]", "vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', concat(parameters('virtualMachineName'), '-vnet'))]", "subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]", - "UriFileNamePieces": "[split(parameters('postDeploymentPsScriptUrl'), '/')]", - "firstFileNameString": "[variables('UriFileNamePieces')[sub(length(variables('UriFileNamePieces')), 1)]]", - "firstFileNameBreakString": "[split(variables('firstFileNameString'), '?')]", - "firstFileName": "[variables('firstFileNameBreakString')[0]]", - "postDeploymentScriptArguments": "[concat('-GitHubActionsRunnerToken ', parameters('githubActionsRunnerToken'), ' -GithubActionsRunnerRegistrationUrl ', parameters('githubActionsRunnerRegistrationUrl'), ' -GithubActionsRunnerName ', parameters('virtualMachineName'), ' -StopService ', parameters('stopService'), ' -GitHubActionsRunnerPath ', parameters('githubActionsRunnerPath'))]" + "postDeploymentScriptArguments": "[concat('-GitHubActionsRunnerToken ', parameters('githubActionsRunnerToken'), ' -GithubActionsRunnerRegistrationUrl ', parameters('githubActionsRunnerRegistrationUrl'), ' -GithubActionsRunnerName ', parameters('virtualMachineName'), ' -Ephemeral ', parameters('ephemeral'), ' -StopService ', parameters('stopService'), ' -GitHubActionsRunnerPath ', parameters('githubActionsRunnerPath'))]", + "publicIpAddressName1": "[if(equals(parameters('publicIpAddressName1'), ''), 'dummy', parameters('publicIpAddressName1'))]", + "publicIpAddressId": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName1'))]", + "properties": { + "deleteOption": "[parameters('pipDeleteOption')]" + } + } }, "resources": [ { @@ -127,7 +143,7 @@ "dependsOn": [ "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIpAddressName1'))]" + "[concat('Microsoft.Network/publicIpAddresses/', variables('publicIpAddressName1'))]" ], "properties": { "ipConfigurations": [ @@ -138,12 +154,7 @@ "id": "[variables('subnetRef')]" }, "privateIPAllocationMethod": "Dynamic", - "publicIpAddress": { - "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName1'))]", - "properties": { - "deleteOption": "[parameters('pipDeleteOption')]" - } - } + "publicIpAddress": "[if(not(equals(parameters('publicIpAddressName1'), '')), variables('publicIpAddressId'), null())]" } } ], @@ -175,7 +186,8 @@ } }, { - "name": "[parameters('publicIpAddressName1')]", + "condition": "[not(equals(parameters('publicIpAddressName1'), ''))]", + "name": "[variables('publicIpAddressName1')]", "type": "Microsoft.Network/publicIpAddresses", "apiVersion": "2020-08-01", "location": "[parameters('location')]", @@ -263,11 +275,8 @@ "type": "CustomScriptExtension", "typeHandlerVersion": "1.9", "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": "[split(parameters('postDeploymentPsScriptUrl'), ' ')]" - }, "protectedSettings": { - "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ', variables('firstFileName'), ' ', variables('postDeploymentScriptArguments'))]" + "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -Command \"[System.IO.File]::WriteAllBytes(\\\"tmp.zip\\\", [System.Convert]::FromBase64String(\\\"', parameters('postDeploymentScriptZipBase64'), '\\\")); Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory(\\\"tmp.zip\\\", \\\".\\\"); & .\\', parameters('postDeploymentScriptFileName'), ' ', variables('postDeploymentScriptArguments'), '\"')]" } } } diff --git a/azure-self-hosted-runners/post-deployment-script.ps1 b/azure-self-hosted-runners/post-deployment-script.ps1 index ac727c47..b663274c 100644 --- a/azure-self-hosted-runners/post-deployment-script.ps1 +++ b/azure-self-hosted-runners/post-deployment-script.ps1 @@ -15,6 +15,10 @@ param ( [ValidateNotNullOrEmpty()] [string]$GithubActionsRunnerName, + [Parameter(Mandatory = $false, HelpMessage = "Start an ephemeral runner (this is the default)")] + [ValidateSet('true', 'false')] + [string]$Ephemeral = 'true', + [Parameter(Mandatory = $false, HelpMessage = "Stop Service immediately (useful for spinning up runners preemptively)")] [ValidateSet('true', 'false')] [string]$StopService = 'true', @@ -261,12 +265,17 @@ Write-Output "Installing GitHub Actions runner $($GitHubAction.Tag) as a Windows Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory($GitHubAction.OutFile, $GitHubActionsRunnerPath) -Write-Output "Configuring the runner to shut down automatically after running" -Set-Content -Path "${GitHubActionsRunnerPath}\shut-down.ps1" -Value "shutdown -s -t 60 -d p:4:0 -c `"workflow job is done`"" -[System.Environment]::SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "${GitHubActionsRunnerPath}\shut-down.ps1", [System.EnvironmentVariableTarget]::Machine) +If ($Ephemeral -ne 'true') { + $EphemeralOption = "" +} Else { + $EphemeralOption = "--ephemeral" + Write-Output "Configuring the runner to shut down automatically after running" + Set-Content -Path "${GitHubActionsRunnerPath}\shut-down.ps1" -Value "shutdown -s -t 60 -d p:4:0 -c `"workflow job is done`"" + [System.Environment]::SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "${GitHubActionsRunnerPath}\shut-down.ps1", [System.EnvironmentVariableTarget]::Machine) +} Write-Output "Configuring the runner" -cmd.exe /c "${GitHubActionsRunnerPath}\config.cmd" --unattended --ephemeral --name ${GithubActionsRunnerName} --runasservice --labels $($GitHubAction.RunnerLabels) --url ${GithubActionsRunnerRegistrationUrl} --token ${GitHubActionsRunnerToken} +cmd.exe /c "${GitHubActionsRunnerPath}\config.cmd" --unattended $EphemeralOption --name ${GithubActionsRunnerName} --runasservice --labels $($GitHubAction.RunnerLabels) --url ${GithubActionsRunnerRegistrationUrl} --token ${GitHubActionsRunnerToken} # Ensure that the service was created. If not, exit with error code. if ($null -eq (Get-Service -Name "actions.runner.*")) {