liveBook · Manning

Active Setup

As explained in Practical Automation with PowerShell: Effective scripting from the console to the cloud

Active Setup is a Windows installation technique that performs per-user configuration after software is installed in a system context.

Active Setup handles the gap between machine-wide installation and per-user configuration on Windows. It executes a command once for each user during sign-in, before the desktop loads and at a higher privilege level than RunOnce. That makes it useful when software must be installed under the system account but still needs settings written in each user’s context, such as a Git default branch. section 11.4.2 section 11.4.3

The mechanism is registered under HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components, so a system-context process can create it. Each component stores a version and an execution command. At login, Windows compares the machine version with the copy held in the current user hive; a missing or older user value causes the command to run, after which the user-side version is recorded. Raising the component version therefore causes the updated command to run again for users. section 11.4.3

Treat the system installation and user customization as separate tasks. The system task can install shared binaries and machine-level settings, while the Active Setup command applies user-level state. This separation matters because a process running as system has no user profile or HKCU access, so configuration that appears global to an interactive user may otherwise be absent. section 11.4.2 section 11.4.3

A maintainable Active Setup implementation keeps registration, script storage, and observability aligned:

  • Create a consistently named component and assign an explicit version so that changes can be deliberately redeployed to users. section 11.4.3
  • Store the generated script under ProgramData, using its component name and version, rather than leaving an untracked command in the registry. section 11.4.3
  • Write each user’s log under that user’s local application-data folder with the same component name, creating a one-to-one mapping among registry key, script, and log. section 11.4.3
  • Wrap execution with PowerShell Start-Transcript so failures can be investigated after sign-in. section 11.4.3

Logging is essential because Active Setup runs before the desktop is available: errors are not visible to the user. A current-user registry value can confirm that the mechanism ran, but it cannot confirm that the command succeeded. It also does not preserve the contents of an external script if that script is later removed or changed. Persisted scripts and per-user transcripts provide the missing audit trail. section 11.4.3

Listing 11.4 New-ActiveSetup
Function New-ActiveSetup {
    param(
        [string]$Name,
        [System.Management.Automation.ScriptBlock]$ScriptBlock,
        [version]$Version = '1.0.0.0'
    )
 
    $ActiveSetupReg =                                             #1
    'HKLM:\Software\Microsoft\Active Setup\Installed Components'
 
    $Item = @{                                                    #2
        Path  = $ActiveSetupReg
        Name  = $Name
        Force = $true
    }
    $ActiveSetup = New-Item @Item | Select-Object -ExpandProperty PSPath
 
    $DefaultPath = 'ActiveSetup\{0}_v{1}.ps1'                     #3
    $ChildPath = $DefaultPath -f $Name, $Version
    $ScriptPath = Join-Path -Path $env:ProgramData -ChildPath $ChildPath
    $ScriptFolder = Split-Path -Path $ScriptPath
 
    if (-not(Test-Path -Path $ScriptFolder)) {                    #4
        New-Item -type Directory -Path $ScriptFolder | Out-Null
    }
 
    $WrapperScript = {                                            #5
        param($Name,$Version)
        $Path = "ActiveSetup\$($Name)_$($Version).log"
        $log = Join-Path $env:APPDATA $Path
        $Transcript = @{ Path = $log; Append = $true;
        IncludeInvocationHeader = $true; Force = $true}
        Start-Transcript @Transcript
        try{
            {0}
        }
        catch{ Write-Host $_ }
        finally{ Stop-Transcript }
    }
 
    $WrapperString = $WrapperScript.ToString()                    #6
    $WrapperString = $WrapperString.Replace('{','{{')
    $WrapperString = $WrapperString.Replace('}','}}')
    $WrapperString = $WrapperString.Replace('{{0}}','{0}')
 
    $WrapperString -f $ScriptBlock.ToString() |                   #7
        Out-File -FilePath $ScriptPath -Encoding utf8
 
    $args = @{                                                    #8
        Path  = $ActiveSetup
        Force = $true
    }
    $ActiveSetupValue = 'powershell.exe -ExecutionPolicy bypass ' +
    "-File ""$($ScriptPath.Replace('\', '\\'))""" +
    " -Name ""$($Name)"" -Version ""$($Version)"""
    Set-ItemProperty @args -Name '(Default)' -Value $Name
    Set-ItemProperty @args -Name 'Version' -Value $Version
    Set-ItemProperty @args -Name 'StubPath' -Value $ActiveSetupValue
}

New-ActiveSetup packages component creation, versioned ProgramData script generation, and registration into a reusable PowerShell function. Its parameters make the component name, script block, and version explicit deployment inputs. section 11.4.3

Git installation with per-user defaults

Install Git and set machine-wide behavior while running as system, then register an Active Setup script that runs `git config --global init.defaultBranch main` for each signing-in user. Before applying the per-user command, configuration shows only the system default branch (`master`); after the user phase, the global default branch is `main`. Test from a clean installation and user configuration, then inspect the scoped Git configuration after login. section 11.4.2 section 11.4.3

Listing 11.5 Git install with Active Setup
Function New-ActiveSetup {
    <#
    Code from listing 4
    #>
}
 
choco install git.install -y                     #1
 
$alias = @{                                      #2
    Name = 'git'
    Value = (Join-Path $Env:ProgramFiles 'Git\bin\git.exe')
}
New-Alias @alias -force
 
git config --system core.autocrlf true           #3
 
$ScriptBlock = {                                 #4
    git config --global init.defaultBranch main
    git config --global --list
}
 
New-ActiveSetup -Name 'Git' -ScriptBlock $ScriptBlock -Version '1.0'

The Git example separates Chocolatey installation and system configuration from the script block that establishes the user-level global Git setting, then registers that block as version 1.0. section 11.4.3

How Practical Automation with PowerShell: Effective scripting from the console to the cloud covers it

How deep it goes
Explains registry version comparison, then develops a reusable PowerShell wrapper with script storage and transcript logging.
Examples
Demonstrates Chocolatey Git installation, system Git settings, a per-user default branch, scoped configuration checks, and reinstall cleanup.
What it assumes
Assumes ability to run PowerShell, install Git with Chocolatey, and distinguish system from interactive-user execution contexts.
What you can do after
Build it from scratch with a versioned registry component, persisted PowerShell script, and per-user execution log.
How it connects
Connects system accounts, user profiles, HKCU, HKLM, RunOnce, PowerShell, Start-Transcript, and Git configuration.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Practical Automation with PowerShell: Effective scripting from the console to the cloud. The section links point to the parts of the book it draws on.