I have a PowerShell script which is passed as user data script in terraform for Windows Server 2022 VM creation in AWS cloud. This PowerShell script uses Jenkins API to create the node and connect the agent with the Jenkins server. But when the VM used in pipeline at the time of executing selenium scripts, it executes the test cases in headless mode. I want to make sure the selenium scripts ran through Jenkins pipeline to be executed in non-headless mode.
Below is the User data script I used to create and connect the agent with Jenkins server
<powershell>#*******************************************************************************# (c) Copyright HCL Technologies Ltd. 2020-2025 All rights reserved.#*******************************************************************************# ================================# Jenkins credentials and URL# ================================New-Item -Path "C:\UserDataRan.txt" -ItemType File -ForceAdd-Content -Path "C:\UserDataRan.txt" -Value "Script executed at $(Get-Date)"# Start transcript to capture all outputStart-Transcript -Path "C:\ProgramData\Amazon\EC2Launch\log\UserDataExecution.log" -Append# Enable verbose output to 'Continue' for debugging$VerbosePreference = "SilentlyContinue"$PSDefaultParameterValues['Import-Module:Verbose'] = $falseWrite-Verbose "Starting user data script..."$JENKINS_URL = "jenkins-server-url"$USER = "user-name"$API_TOKEN = "api-token"# ================================# Get IP and build node name# ================================$NODE_IP = (Get-NetIPAddress -AddressFamily IPv4 ` | Where-Object { $_.IPAddress -notlike "169.*" -and $_.IPAddress -notlike "127.*" } ` | Select-Object -First 1 -ExpandProperty IPAddress)$NODE_NAME = "plan-win-$NODE_IP"# ================================# Get CSRF crumb# ================================$pair = "$USER`:$API_TOKEN"$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)$base64 = [Convert]::ToBase64String($bytes)$headers = @{ Authorization = "Basic $base64" }$crumbJson = Invoke-RestMethod -Uri "$JENKINS_URL/crumbIssuer/api/json" -Headers $headers$CRUMB_FIELD = $crumbJson.crumbRequestField$CRUMB = $crumbJson.crumb# ================================# Create node# ================================try { Write-Host "[+] Creating new node $NODE_NAME" Invoke-RestMethod -Uri "$JENKINS_URL/computer/doCreateItem?name=$NODE_NAME&mode=hudson.slaves.DumbSlave&type=hudson.slaves.DumbSlave&json=%7B%7D" ` -Method Post ` -Headers ($headers + @{ $CRUMB_FIELD = $CRUMB }) ` -ContentType "application/xml" | Out-Null} catch { Write-Host "[!] Warning: Node creation returned an error, but proceeding: $_"}# ================================# Node config XML# ================================$CONFIG_FILE = @"<?xml version="1.1" encoding="UTF-8"?><slave><name>$NODE_NAME</name><description>Windows node $NODE_NAME for PR cucumber testcases.</description><remoteFS>D:\jenkins</remoteFS><numExecutors>1</numExecutors><mode>EXCLUSIVE</mode><retentionStrategy class="hudson.slaves.RetentionStrategy`$Always"/><launcher class="hudson.slaves.JNLPLauncher"><workDirSettings><disabled>false</disabled><internalDir>remoting</internalDir><failIfWorkDirIsMissing>false</failIfWorkDirIsMissing></workDirSettings><webSocket>false</webSocket></launcher><label>$NODE_NAME terraform</label><nodeProperties><hudson.slaves.EnvironmentVariablesNodeProperty><envVars serialization="custom"><tree-map><default><comparator class="java.lang.String`$CaseInsensitiveComparator"/></default><int>2</int><string>RATIONAL_COMMON</string><string>C:\Program Files\DevOps\Plan</string><string>WGET_HOME</string><string>C:\ProgramData\chocolatey\bin\wget.exe</string></tree-map></envVars></hudson.slaves.EnvironmentVariablesNodeProperty><hudson.tools.ToolLocationNodeProperty><locations><hudson.tools.ToolLocationNodeProperty_-ToolLocation><type>hudson.plugins.git.GitTool`$DescriptorImpl</type><name>Default</name><home>C:\Program Files\Git\cmd\git.exe</home></hudson.tools.ToolLocationNodeProperty_-ToolLocation></locations></hudson.tools.ToolLocationNodeProperty></nodeProperties></slave>"@Write-Host "[+] Updating config for $NODE_NAME"Invoke-RestMethod -Uri "$JENKINS_URL/computer/$NODE_NAME/config.xml" ` -Method Post ` -Headers ($headers + @{ $CRUMB_FIELD = $CRUMB }) ` -ContentType "application/xml" ` -Body $CONFIG_FILE | Out-NullWrite-Host "[+] Node created and config updated."# ================================# Fetch JNLP XML# ================================try { Write-Host "[+] Fetching JNLP XML for $NODE_NAME" $response = Invoke-WebRequest -Uri "$JENKINS_URL/computer/$NODE_NAME/slave-agent.jnlp" -Headers $headers -UseBasicParsing if ($response.StatusCode -ne 200) { Write-Host "[!] Error: Received HTTP $($response.StatusCode)" exit 1 } $xmlString = [System.Text.Encoding]::UTF8.GetString($response.Content) [xml]$xmlDoc = $xmlString} catch { Write-Host "[!] Error fetching or parsing JNLP XML: $_" exit 1}# ================================# Extract the secret# ================================try { Write-Host "[+] Extracting secret from JNLP XML" $argumentNodes = $xmlDoc.SelectNodes("//jnlp/application-desc/argument") if ($argumentNodes.Count -ge 1) { $SECRET = $argumentNodes[0].InnerText Write-Host "[+] Secret extracted: $SECRET" } else { Write-Host "[!] Error: No <argument> nodes found." exit 1 }} catch { Write-Host "[!] Error extracting secret: $_" exit 1}$RUN_CMD = "java -jar agent.jar -url $JENKINS_URL/ -secret $SECRET -name $NODE_NAME -webSocket -workDir D:\jenkins"Write-Host $RUN_CMD# ================================# Run the Jenkins agent# ================================$agentDir = "D:\jenkins"if (-Not (Test-Path $agentDir)) { Write-Host "[!] Error: Agent directory '$agentDir' does not exist." exit 1}Set-Location -Path $agentDir# Run the Java agent commandInvoke-Expression $RUN_CMD#$process = Start-Process -FilePath "java" `# -ArgumentList "-jar agent.jar -url $JENKINS_URL/ -secret $SECRET -name $NODE_NAME -webSocket -workDir D:\jenkins" `# -WorkingDirectory "D:\jenkins" `# -NoNewWindow `# -PassThruWrite-Host "[+] Jenkins agent started with PID $($process.Id)"Write-Verbose "Script completed."Stop-Transcript</powershell>This above script connects the agent in non-interactive mode.I tried setting AutoLogon and used Windows Service Wrapper for the selenium script to be executed in GUI (non-headless mode). But it didn't worked out.
Below are the scripts I tried.
Script 1: Windows Service Wrapper
# Create WinSW configuration file (non-interactive)Write-Host "Creating WinSW configuration file..."$winswConfig = @"<service><id>$serviceName</id><name>Jenkins Agent</name><description>Jenkins Agent Service for running CI/CD pipelines</description><executable>java</executable><arguments>-jar "$agentDir\agent.jar" -url $JENKINS_URL -secret $SECRET -name $NODE_NAME -webSocket -workDir "$agentDir"</arguments><logmode>rotate</logmode><workingdirectory>$agentDir</workingdirectory><onfailure action="restart" delay="10 sec"/></service>"@try { Set-Content -Path $winswConfigPath -Value $winswConfig -ErrorAction Stop Write-Host "WinSW configuration created at $winswConfigPath"} catch { Write-Host "[!] Error: Failed to create WinSW configuration file: $_" exit 1}# Install the WinSW service (non-interactive)Write-Host "Installing Jenkins Agent as a Windows service..."Set-Location -Path $agentDirtry {& $winswPath install Write-Host "Service installation command executed."} catch { Write-Host "[!] Error: Failed to install service: $_" exit 1}# Verify service installationWrite-Host "Verifying service installation..."$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinueif (-not $service) { Write-Host "[!] Error: Service '$serviceName' not found. Installation failed." Write-Host "Check '$winswConfigPath' for errors and ensure '$winswPath' has execute permissions." exit 1}Write-Host "Service '$serviceName' installed successfully."# Modify service properties: Set user account (non-interactive)Write-Host "Configuring service to run under user account '$userAccount'..."$scResult = sc.exe config $serviceName obj= $userAccount password= $userPasswordif ($LASTEXITCODE -ne 0) { Write-Host "[!] Error: Failed to configure service account: $scResult" exit 1}Write-Host "Service account configured."# Attempt to enable "Allow service to interact with desktop" (non-interactive)Write-Host "Enabling 'Allow service to interact with desktop' (if supported)..."$scResult = sc.exe config $serviceName type= own type= interactif ($LASTEXITCODE -ne 0) { Write-Host "[!] Warning: Failed to enable desktop interaction: $scResult" Write-Host "Setting up scheduled task for interactive user session to ensure GUI support..." # Create batch file for scheduled task $batchContent = @"@echo offcd /d $agentDirjava -jar agent.jar -url $JENKINS_URL -secret $SECRET -name $NODE_NAME -webSocket -workDir $agentDir"@ try { Set-Content -Path $batchFile -Value $batchContent -ErrorAction Stop Write-Host "Batch file created at $batchFile" # Create scheduled task $action = New-ScheduledTaskAction -Execute "$batchFile" $trigger = New-ScheduledTaskTrigger -AtLogOn -User $userAccount $principal = New-ScheduledTaskPrincipal -UserId $userAccount -LogonType Interactive -RunLevel Highest Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Description "Run Jenkins Agent at logon" -Force -ErrorAction Stop Write-Host "Scheduled task '$taskName' created for interactive session." } catch { Write-Host "[!] Error: Failed to create scheduled task: $_" exit 1 }} else { Write-Host "Desktop interaction enabled (if supported)." # Start the service (non-interactive) Write-Host "Starting Jenkins Agent service..." try {& $winswPath start Start-Sleep -Seconds 5 # Wait for service to start $service = Get-Service -Name $serviceName if ($service.Status -ne "Running") { Write-Host "[!] Error: Service failed to start. Status: $($service.Status)" Write-Host "Check WinSW logs at '$agentDir\JenkinsAgent.wrapper.log'." Write-Host "Setting up scheduled task as fallback..." Set-Content -Path $batchFile -Value $batchContent -ErrorAction Stop Write-Host "Batch file created at $batchFile" $action = New-ScheduledTaskAction -Execute "$batchFile" $trigger = New-ScheduledTaskTrigger -AtLogOn -User $userAccount $principal = New-ScheduledTaskPrincipal -UserId $userAccount -LogonType Interactive -RunLevel Highest Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Description "Run Jenkins Agent at logon" -Force -ErrorAction Stop Write-Host "Scheduled task '$taskName' created for interactive session." } else { Write-Host "Service started successfully." } } catch { Write-Host "[!] Error: Failed to start service: $_" Write-Host "Check WinSW logs at '$agentDir\JenkinsAgent.wrapper.log'." exit 1 }}# Verify service or task statusWrite-Host "Checking final status..."try { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service -and $service.Status -eq "Running") { Write-Host "Service Status: $($service.Status)" Write-Host "Service StartType: $($service.StartType)" $serviceDetails = Get-WmiObject -Class Win32_Service -Filter "Name='$serviceName'" | Select-Object Name, StartName, DesktopInteract Write-Host "Service Properties: Name=$($serviceDetails.Name), StartName=$($serviceDetails.StartName), DesktopInteract=$($serviceDetails.DesktopInteract)" } else { Write-Host "Service is not running. Checking scheduled task..." $task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue if ($task) { Write-Host "Scheduled task '$taskName' is configured. Ensure '$userAccount' is logged in for GUI support." } else { Write-Host "[!] Error: Neither service nor scheduled task is configured properly." exit 1 } }} catch { Write-Host "[!] Error: Failed to verify status: $_" exit 1}# Output completion messageWrite-Host "Setup complete. Jenkins agent is configured."Write-Host "If using the scheduled task, ensure '$userAccount' is logged into an interactive session (e.g., via AutoLogon or Remote Desktop)."Write-Host "Test Selenium pipelines to verify GUI mode. Check WinSW logs at '$agentDir\JenkinsAgent.wrapper.log' if issues occur."& $winswPath stop& $winswPath startScript 2: AutoLogon
# Set up AutoLogon for the Administrator user (registry method - not secure, use for test environments only)$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"Set-ItemProperty -Path $regPath -Name "AutoAdminLogon" -Value "1" -Type StringSet-ItemProperty -Path $regPath -Name "DefaultUserName" -Value $userAccount -Type StringSet-ItemProperty -Path $regPath -Name "DefaultPassword" -Value $userPassword -Type String# Create batch file to run the agent$batchContent = @"@echo offcd /d $agentDirjava -jar agent.jar -url $jenkinsUrl/ -secret $secret -name "$nodeName" -webSocket -workDir "$agentDir""@Set-Content -Path $batchFile -Value $batchContent -ErrorAction Stop# Create scheduled task to run the agent at logon in interactive session$action = New-ScheduledTaskAction -Execute "$batchFile"$trigger = New-ScheduledTaskTrigger -AtLogOn -User $userAccount$principal = New-ScheduledTaskPrincipal -UserId $userAccount -LogonType Interactive -RunLevel HighestRegister-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Description "Run Jenkins Agent at logon" -Force -ErrorAction Stop# Reboot the VM to apply AutoLogon and trigger the taskRestart-Computer -ForceScript 3: AutoLogon and Scheduled Task
# Configure AutoLogon (registry method, use cautiously)Write-Log "Configuring AutoLogon for '$userAccount'..."$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"try { Set-ItemProperty -Path $regPath -Name "AutoAdminLogon" -Value "1" -Type String Set-ItemProperty -Path $regPath -Name "DefaultUserName" -Value $userAccount -Type String Set-ItemProperty -Path $regPath -Name "DefaultPassword" -Value $userPassword -Type String Write-Log "AutoLogon configured." Write-Host "AutoLogon configured."} catch { Write-Log "[!] Error: Failed to configure AutoLogon: $_" Write-Host "[!] Error: Failed to configure AutoLogon: $_" exit 1}# Disable screen lock to ensure desktop availabilityWrite-Log "Disabling screen lock..."try { Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "DisableLockWorkstation" -Value 1 -Type DWord -Force Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaveActive" -Value 0 -Type String -Force Write-Log "Screen lock disabled." Write-Host "Screen lock disabled."} catch { Write-Log "[!] Warning: Failed to disable screen lock: $_" Write-Host "[!] Warning: Failed to disable screen lock: $_"}# Ensure display is active (for cloud VMs)Write-Log "Configuring display settings..."try { # Set resolution to ensure GUI rendering Set-DisplayResolution -Width 1920 -Height 1080 -ErrorAction SilentlyContinue Write-Log "Display resolution set to 1920x1080." Write-Host "Display resolution set to 1920x1080."} catch { Write-Log "[!] Warning: Failed to set display resolution: $_" Write-Host "[!] Warning: Failed to set display resolution: $_"}# Create batch file to run the agentWrite-Log "Creating batch file at $batchFile..."$batchContent = @"@echo offcd /d $agentDirjava -jar agent.jar -url $jenkinsUrl/ -secret $secret -name "$nodeName" -webSocket -workDir "$agentDir" >> $agentDir\agent.log 2>&1"@try { Set-Content -Path $batchFile -Value $batchContent -ErrorAction Stop Write-Log "Batch file created at $batchFile" Write-Host "Batch file created at $batchFile"} catch { Write-Log "[!] Error: Failed to create batch file: $_" Write-Host "[!] Error: Failed to create batch file: $_" exit 1}# Create scheduled task to run the agent at logonWrite-Log "Creating scheduled task '$taskName'..."try { $action = New-ScheduledTaskAction -Execute "$batchFile" $trigger = New-ScheduledTaskTrigger -AtLogOn -User $userAccount $principal = New-ScheduledTaskPrincipal -UserId $userAccount -LogonType Interactive -RunLevel Highest Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Description "Run Jenkins Agent at logon for GUI support" -Force -ErrorAction Stop Write-Log "Scheduled task '$taskName' created." Write-Host "Scheduled task '$taskName' created."} catch { Write-Log "[!] Error: Failed to create scheduled task: $_" Write-Host "[!] Error: Failed to create scheduled task: $_" exit 1}# Verify user session after reboot (for logging)Write-Log "Checking for active user session (pre-reboot)..."$session = qwinsta | Select-String $userAccount | ForEach-Object { $_.Line -split '\s+' }if ($session -and $session[3] -eq "Active") { Write-Log "Active session found for '$userAccount'." Write-Host "Active session found for '$userAccount'."} else { Write-Log "[!] Warning: No active session found for '$userAccount'. GUI mode may not work until reboot." Write-Host "[!] Warning: No active session found for '$userAccount'."}# Reboot the VM to apply AutoLogon and trigger the taskWrite-Log "Rebooting VM to apply AutoLogon and start scheduled task..."Write-Host "Rebooting VM to apply AutoLogon and start scheduled task..."Restart-Computer -ForceInvestigation about the above scripts:All the above scripts worked partially, they were able to connect the agent with the jenkins server but when the pipeline triggered the selenium scripts they ran in headless mode.
My Requirement:I will be running the script in userdata which means the jenkins agent will be connected in non-interactive way but that does not affect the selenium scripts to be executed in GUI (non-headless mode) when executed through Jenkins pipeline. I don't mind configuring the VM in manually way because I will create an AMI out of the VM that I configured to use in terraform.







