← Back to blog

Better together: Login Enterprise & Hydra (Part 2)

This is part two of a series walking through how Hydra can integrate with Login Enterprise for further automated testing capabilities. If you haven’t read that article, check this article out Better Together: Login Enterprise & Hydra (Part One)

blue and white robot riding a multi headed dragon into battle

That blog talks about how Hydra can orchestrate fully-automated Launcher deployments, leveraging an Azure Marketplace vanilla image customized by Hydra Script Collections. This time we’ll focus on the Session Host, or in LE terms– the testing Target. Once you setup your Launcher, follow these steps to enable easier Login Enterprise testing orchestrated by Hydra.

The workflow

In Login Enterprise, there is one primary dependency here, the Logon Executable, whose primary responsibility is to trigger automation scripts once a Virtual User logs in. The process looks like:

  1. Virtual User logs into Windows Desktop
  2. Logon Executable is triggered by Startup (e.g., GPO, Registry, Startup folder placement)
  3. Logon Executable “calls home” to download dependencies and initiate its job

Configuring the Logon Executable

This script is very similar to the Launcher Installation and Startup Shortcut configuration covered in Part 1, except this can be directly downloaded from a Login Enterprise Virtual Appliance.

The PowerShell script automates the configuration by:

  • downloading the standalone Logon Executable from the specified appliance
  • creating a shortcut in the Startup folder

Anyway, here is the code:

OutputWriter("Downloading Logon EXE")
 
# Base FQDN of the appliance
$applianceFQDN = "https://<your_login_enterprise_fqdn_here>"
 
# URL of the ZIP file to download
$url = "$applianceFQDN/contentDelivery/api/logonApp"
 
# Arguments to pass to the EXE (adjust as needed)
$arguments = $applianceFQDN
 
# Define temp paths
$tempDir    = "C:\LoginVSI"
$zipName    = [IO.Path]::GetFileName($url)                    
$zipPath    = Join-Path $tempDir "$zipName.zip"                     
$extractDir = Join-Path $tempDir ([IO.Path]::GetFileNameWithoutExtension($zipName))
 
# Define shortcut properties
 
# Define target executable and Startup shortcut paths
$targetPath = "C:\LoginVSI\logonApp\LoginPI.Logon.exe"
$shortcutPath = "$env:ALLUSERSPROFILE\Microsoft\Windows\Start Menu\Programs\Startup\LoginPI_Logon.lnk"
 
# Ensure the extract directory exists (or recreate it)
if (Test-Path $extractDir) {
    Remove-Item -LiteralPath $extractDir -Recurse -Force
}
New-Item -ItemType Directory -Path $extractDir | Out-Null
 
try {
    # Download the ZIP file
    Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing
 
    # Extract the ZIP into $extractDir; -Force will overwrite if files already exist
    Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
 
    # Find the first .exe in the extracted folder (recursively)
    $exe = Get-ChildItem -Path $extractDir -Filter '*.exe' -Recurse |
           Sort-Object LastWriteTime -Descending |
           Select-Object -First 1
 
    if (-not $exe) {
        throw "No executable (.exe) found in '$extractDir'."
    }
 
    # Create the shortcut
 
try {
     
    # Verify the target executable exists
    if (-not (Test-Path -Path $targetPath -PathType Leaf)) {
        throw "Target executable not found: $targetPath"
    }
 
    # Ensure the Startup folder exists (it should, but just in case)
    $startupFolder = Split-Path -Parent $shortcutPath
    if (-not (Test-Path -Path $startupFolder -PathType Container)) {
        throw "Startup folder does not exist: $startupFolder"
    }
 
    # Create the WScript.Shell COM object
    try {
        $WshShell = New-Object -ComObject WScript.Shell
    }
    catch {
        throw "Unable to create WScript.Shell COM object: $_"
    }
 
    # Create the shortcut
    $shortcut = $WshShell.CreateShortcut($shortcutPath)
 
    # Assign properties to the shortcut
    $shortcut.TargetPath       = $targetPath
    $shortcut.Arguments        = $arguments
    $shortcut.WorkingDirectory = Split-Path -Parent $targetPath
 
    # Save the shortcut to disk
    $shortcut.Save()
 
    Write-Host "Shortcut successfully created at: $shortcutPath"
    OutputWriter("Shortcut created!")
}
catch {
    Write-Error "Failed to create shortcut: $_"
   OutputWriter("Failed to create shortcut! $_")
}
 
 
}
catch {
    Write-Error "An error occurred: $_"
    OutputWriter("An error occurred! $_")
}

Installing the Launcher, UWC, and RDC

The Launcher, Universal Web Connector, and Remote Desktop Client can all be installed using an analogous approach, so I will describe here only the Launcher installation.

The PowerShell script automates the installation by downloading an MSI installer from a specified URL (in this case, hosted in Github) and executing it locally.

The script also creates a Windows Shortcut for the Launcher, which is stored in the “All Users” Startup folder. As you’ll see in the next section, we use a local account configured with SysInternals’ AutoLogon to act as a Launcher service account. When the Autologon user logs in, the Launcher will automatically start, ready for testing.

Here’s the code:

# Set Login Enterprise Details
$serverUrl = "https://<your_login_enterprise_fqdn_here>"
$secret = "<your_launcher_secret_here>"
 
# Set Launcher Installation Defaults and Startup Folder Location
$launcherProgramFilesPath = "C:\Program Files\Login VSI\Login Enterprise Launcher"
$targetPath = Join-Path $launcherProgramFilesPath "LoginEnterprise.Launcher.UI.exe"
$shortcutPath = "$env:ALLUSERSPROFILE\Microsoft\Windows\Start Menu\Programs\Startup\LoginEnterpriseLauncherUI.lnk"
$startupFolder = Split-Path -Parent $shortcutPath
 
 
####################################################################################################
# Download and Install MSI from GitHub
####################################################################################################
$msiUrl        = "https://<URL_for_launcher_executable>" # E.g. add Setup.msi to public Github Repo
$msiName       = "Setup.msi"
$downloadDir   = "C:\Launcher\Installer"
$msiPath       = Join-Path $downloadDir $msiName
 
OutputWriter("Starting MSI download and install process.")
OutputWriter("Installer URL: $msiUrl")
OutputWriter("Installer will be saved to: $msiPath")
 
####################################################################################################
# Create download directory
####################################################################################################
if (-not (Test-Path $downloadDir)) {
    OutputWriter("Creating installer download directory: $downloadDir")
    try {
        New-Item -Path $downloadDir -ItemType Directory -Force | Out-Null
        OutputWriter("Created directory $downloadDir")
    } catch {
        OutputWriter("Failed to create directory: $_")
        OutputWriter("Directory creation failed: $_")
        exit 1
    }
} else {
    OutputWriter("Download directory already exists: $downloadDir")
}
 
####################################################################################################
# Download MSI
####################################################################################################
OutputWriter("Downloading installer...")
try {
    Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath -UseBasicParsing
    OutputWriter("Download completed.")
    OutputWriter("Downloaded $msiName to $msiPath")
} catch {
    OutputWriter("Download failed: $_")
    OutputWriter("Download error: $_")
    exit 1
}
 
####################################################################################################
# Install MSI
####################################################################################################
if (Test-Path $msiPath) {
    OutputWriter("Starting MSI installation...")
    try {
        $arguments = "/i `"$msiPath`" /qn serverurl=$serverUrl secret=$secret"
        OutputWriter("Executing: msiexec.exe $arguments")
        $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $arguments -Wait -PassThru
 
        if ($process.ExitCode -eq 0) {
            OutputWriter("MSI installation succeeded.")
            OutputWriter("Installer exit code: 0 (Success)")
        } else {
            OutputWriter("MSI installation failed with exit code: $($process.ExitCode)")
            OutputWriter("Installer exit code: $($process.ExitCode)")
            exit $process.ExitCode
        }
    } catch {
        OutputWriter("Installation process failed: $_")
        OutputWriter("Installer exception: $_")
        exit 1
    }
} else {
    OutputWriter("MSI file not found at expected path: $msiPath")
    OutputWriter("Installer missing: $msiPath")
    exit 1
}
OutputWriter("MSI process completed.")
 
if (Test-Path $launcherProgramFilesPath) {
    OutputWriter("Launcher installation deemed successful based on installation folder in %PROGRAMFILES%.")
    # exit 0
}
 
##################################################
# Add Launcher to Startup folder
##################################################
OutputWriter("Starting shortcut creation and Startup placement process.")
OutputWriter("Creating shortcut from: $targetPath")
OutputWriter("Shortcut will be added to $startupFolder")
 
try {
     
    # Verify the target executable exists
    if (-not (Test-Path -Path $targetPath -PathType Leaf)) {
        throw "Target executable not found: $targetPath"
    }
 
    # Ensure the Startup folder exists (it should, but just in case)
    if (-not (Test-Path -Path $startupFolder -PathType Container)) {
        throw "Startup folder does not exist: $startupFolder"
    }
 
    # Create the WScript.Shell COM object
    try {
        $WshShell = New-Object -ComObject WScript.Shell
    }
    catch {
        throw "Unable to create WScript.Shell COM object: $_"
    }
 
    # Create the shortcut
    $shortcut = $WshShell.CreateShortcut($shortcutPath)
 
    # Assign properties to the shortcut
    $shortcut.TargetPath       = $targetPath
    $shortcut.Arguments        = $arguments
    $shortcut.WorkingDirectory = Split-Path -Parent $targetPath
 
    # Save the shortcut to disk
    $shortcut.Save()
 
    OutputWriter("Shortcut successfully created at: $shortcutPath")
    # OutputWriter("Shortcut created!")
}
catch {
    # Write-Error "Failed to create shortcut: $_"
    OutputWriter("Failed to create shortcut! $_")
}

Configuring SysInternals Autologon

The PowerShell script below automates the setup of Windows AutoLogon for a local user account. It downloads the SysInternals’ AutoLogon utility, then checks there is a local user account with the specified $autoLogonUsername exists. Otherwise, it creates one with a randomly generated password. The $autoLogonCount variable controls the number of automatic logons that are configured–each restart of the VM will decrement this value until its zero and no further auto logons will occur.

Here’s the code:

####################################################################################################
# Configure AutoLogon
####################################################################################################

$autoLogonCount               = "7" # Configure the number of automatic logins here. Currently, this will configure 7 automatic logins.
$autologonDownloadUrl         = "https://download.sysinternals.com/files/AutoLogon.zip"
$autologonDownloadDestination = "C:\Launcher\AutoLogon"
$autologonZipDestination      = Join-Path $autologonDownloadDestination "AutoLogon.zip" # C:\Launcher\AutoLogon\AutoLogon.zip
$autologonUnzipDestination    = Join-Path $autologonDownloadDestination "AutoLogon"     # C:\Launcher\AutoLogon\AutoLogon\
$autologonExePath             = Join-Path $autologonUnzipDestination "AutoLogon64.exe"  # C:\Launcher\AutoLogon\AutoLogon\AutoLogon64.exe
 
$autologonUsername            = "autologin" # This is the username of the local user account, used for AutoLogon. You may configure this value.
Add-Type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword(20, 4) # A randomized password is created
$securePass = ConvertTo-SecureString $password -AsPlainText -Force
 
OutputWriter("Downloading SysInternals' AutoLogon from: $autologonDownloadUrl")
OutputWriter("Archive will be downloaded to: $$autologonUnzipDestination")
 
OutputWriter("Archive will be extracted to: $autologonUnzipDestination")
OutputWriter("Target executable should be in: $autologonExePath")
 
##################################################
# Prepare for download and extraction
##################################################
if (-not (Test-Path $autologonDownloadDestination)) {
    OutputWriter("Creating folder to store Autologon download")
    New-Item -Path $autologonDownloadDestination -ItemType Directory -Force | Out-Null
}
else { 
    # OutputWriter("Folder already exists.")
    OutputWriter("Autologon download folder already exists.")
}
 
##################################################
# Download AutoLogon and Extract
##################################################
OutputWriter("Downloading SysInternals' AutoLogon")
if (-not (Test-Path $autologonExePath)) {
    OutputWriter("AutoLogon64.exe not found. Proceeding to download and extract...")
     
    try {
        Invoke-WebRequest -Uri $autologonDownloadUrl -OutFile $autologonZipDestination -UseBasicParsing
        Expand-Archive -Path $autologonZipDestination -DestinationPath $autologonUnzipDestination -Force
        OutputWriter("Download and extraction complete.")
    }
    catch {
        OutputWriter("Failed to download or extract AutoLogon: $_")
        exit 1
    }
} else {
    OutputWriter("AutoLogon already downloaded and extracted.")
}
 
 
####################################################################################################
# Create autologon user (if not exists)
####################################################################################################
try {
    if (-not (Get-LocalUser -Name $autologonUsername -ErrorAction SilentlyContinue)) {
        OutputWriter("Creating local user '$autologonUsername'")
        New-LocalUser -Name $autologonUsername -Password $securePass -FullName $autologonUsername -PasswordNeverExpires:$true -UserMayNotChangePassword:$true
        OutputWriter("User '$autologonUsername' created.")
    } else {
        OutputWriter("User '$autologonUsername' already exists.")
    }
}
catch {
    OutputWriter("Failed to create or check user: $_")
    throw "Failed to create or check for user existence: $_"
}
 
####################################################################################################
# Configure AutoLogon using AutoLogon64.exe
####################################################################################################
if (Test-Path $autologonExePath) {
    try {
        OutputWriter "Running AutoLogon64.exe configuration..."
        Start-Process $autologonExePath -ArgumentList $autologonUsername,$env:COMPUTERNAME,$password,"-accepteula" -Wait
        OutputWriter("AutoLogon configured.")
    }
    catch {
        OutputWriter("Failed to configure AutoLogon: $_")
        exit 1
    }
} else {
    OutputWriter("AutoLogon64.exe not found at expected path: $autologonExePath")
    exit 1
}
 
####################################################################################################
# Registry configuration
####################################################################################################
OutputWriter("Configuring registry values for AutoLogon...")
 
$winlogonPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
$policyPath   = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
 
# Ensure required policies
try {
    $regSettings = @{
        "$policyPath\dontdisplaylastusername"           = 0
        "$policyPath\DisableAutomaticRestartSignOn"     = 0
        "$winlogonPath\AutoLogonCount"                  = $autoLogonCount
    }
 
    foreach ($key in $regSettings.Keys) {
        $pathParts = $key.Split('\')
        $regPath = ($pathParts[0..($pathParts.Length - 2)] -join '\')
        $regName = $pathParts[-1]
        $desiredValue = $regSettings[$key]
 
        $existing = Get-ItemProperty -Path $regPath -Name $regName -ErrorAction SilentlyContinue
        if ($existing.$regName -ne $desiredValue) {
            Set-ItemProperty -Path $regPath -Name $regName -Value $desiredValue
            OutputWriter("Set registry '$regName' to '$desiredValue'")
        } else {
            OutputWriter("Registry '$regName' already set to '$desiredValue'")
        }
    }
}
catch {
    OutputWriter("Failed to update registry keys: $_")
    exit 1
}
OutputWriter("AutoLogon setup complete")

What’s next?

This was the first series that covered how Hydra and Login Enterprise can operate better together. Perhaps there will be similar installments down the line, so look out for those. Thanks for reading.