Skip to content

Instantly share code, notes, and snippets.

@rajbos
Created July 15, 2020 14:17
Show Gist options
  • Select an option

  • Save rajbos/49f70f4e2b9765da05f0526225de2450 to your computer and use it in GitHub Desktop.

Select an option

Save rajbos/49f70f4e2b9765da05f0526225de2450 to your computer and use it in GitHub Desktop.
Register Windows Startup/Shutdown script
function Register-EventScript {
param (
[string] $eventToRegister, # Either Startup or Shutdown
[string] $pathToScript,
[string] $scriptParameters
)
$path = "$ENV:systemRoot\System32\GroupPolicy\Machine\Scripts\$eventToRegister"
if (-not (Test-Path $path)) {
# path HAS to be available for this to work
New-Item -path $path -itemType Directory
}
# Add script to Group Policy through the Registry
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\$eventToRegister\0\0",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\$eventToRegister\0\0" |
ForEach-Object {
if (-not (Test-Path $_)) {
New-Item -path $_ -force
}
}
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\$eventToRegister\0",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\$eventToRegister\0" |
ForEach-Object {
New-ItemProperty -path "$_" -name DisplayName -propertyType String -value "Local Group Policy" -force
New-ItemProperty -path "$_" -name FileSysPath -propertyType String -value "$ENV:systemRoot\System32\GroupPolicy\Machine" -force
New-ItemProperty -path "$_" -name GPO-ID -propertyType String -value "LocalGPO" -force
New-ItemProperty -path "$_" -name GPOName -propertyType String -value "Local Group Policy" -force
New-ItemProperty -path "$_" -name PSScriptOrder -propertyType DWord -value 2 -force
New-ItemProperty -path "$_" -name SOM-ID -propertyType String -value "Local" -force
}
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\$eventToRegister\0\0",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\$eventToRegister\0\0" |
ForEach-Object {
New-ItemProperty -path "$_" -name Script -propertyType String -value $pathToScript -force
New-ItemProperty -path "$_" -name Parameters -propertyType String -value $scriptParameters -force
New-ItemProperty -path "$_" -name IsPowershell -propertyType DWord -value 1 -force
New-ItemProperty -path "$_" -name ExecTime -propertyType QWord -value 0 -force
}
}
# get crrentlocation so we can form the full path to the script to register
$currentLocation = $PSScriptRoot
# register the script twice
Register-EventScript -eventToRegister "Startup" -pathToScript "$currentLocation\ScriptToRun.ps1" -scriptParameters "OnStartup"
Register-EventScript -eventToRegister "Shutdown" -pathToScript "$currentLocation\ScriptToRun.ps1" -scriptParameters "OnShutdown"
param (
[string] $message
)
function Run-Script {
param (
[string] $message
)
# get the date in our format
$date = Get-Date
Get-date $date -f "yyyyMMdd HH:mm:ss"
# log the date/time and the message
"$($date.ToUniversalTime().ToString("yyyyMMdd HH:mm:ss")) - $message" >> "C:\Temp\Log.txt"
}
# call the script with the incoming parameter
Run-Script -message $message
@jeremiassamuelzitnik
Copy link

Also Works on Windows 11 24H2.

Is posible to check if previously exists other ID and increment without removing previous one?

@jeremiassamuelzitnik
Copy link

jeremiassamuelzitnik commented Sep 11, 2025

Here is my bit. It do it incremental

function Register-EventScript {
    param (
        [string] $eventToRegister, # Either Startup or Shutdown
        [string] $pathToScript,
        [string] $scriptParameters
    )
    $path = "$ENV:systemRoot\System32\GroupPolicy\Machine\Scripts\$eventToRegister"
    if (-not (Test-Path $path)) {
        # Path must exist for this to work
        New-Item -Path $path -ItemType Directory -Force
    }

    # Define base registry paths
    $baseRegPaths = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\$eventToRegister",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\$eventToRegister"
    )

    # Find the next available index for the script
    $index = 0
    foreach ($basePath in $baseRegPaths) {
        $existingKeys = Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^\d+$' } | Select-Object -ExpandProperty PSChildName
        if ($existingKeys) {
            $maxIndex = ($existingKeys | ForEach-Object { [int]$_ } | Measure-Object -Maximum).Maximum
            $index = [math]::Max($index, $maxIndex + 1)
        }
    }

    # Create registry paths with the new index
    $regPaths = $baseRegPaths | ForEach-Object { "$_\$index" }
    $scriptRegPaths = $baseRegPaths | ForEach-Object { "$_\$index\0" }

    # Create registry keys for the new index
    foreach ($regPath in $regPaths) {
        if (-not (Test-Path $regPath)) {
            New-Item -Path $regPath -Force
        }
        # Set properties for the parent key
        New-ItemProperty -Path $regPath -Name DisplayName -PropertyType String -Value "Local Group Policy" -Force
        New-ItemProperty -Path $regPath -Name FileSysPath -PropertyType String -Value "$ENV:systemRoot\System32\GroupPolicy\Machine" -Force
        New-ItemProperty -Path $regPath -Name GPO-ID -PropertyType String -Value "LocalGPO" -Force
        New-ItemProperty -Path $regPath -Name GPOName -PropertyType String -Value "Local Group Policy" -Force
        New-ItemProperty -Path $regPath -Name PSScriptOrder -PropertyType DWord -Value 2 -Force
        New-ItemProperty -Path $regPath -Name SOM-ID -PropertyType String -Value "Local" -Force
    }

    # Create subkeys and set properties for the script
    foreach ($scriptRegPath in $scriptRegPaths) {
        if (-not (Test-Path $scriptRegPath)) {
            New-Item -Path $scriptRegPath -Force
        }
        New-ItemProperty -Path $scriptRegPath -Name Script -PropertyType String -Value $pathToScript -Force
        New-ItemProperty -Path $scriptRegPath -Name Parameters -PropertyType String -Value $scriptParameters -Force
        New-ItemProperty -Path $scriptRegPath -Name IsPowershell -PropertyType DWord -Value 1 -Force
        New-ItemProperty -Path $scriptRegPath -Name ExecTime -PropertyType QWord -Value 0 -Force
    }
}

# get crrentlocation so we can form the full path to the script to register
$currentLocation = $PSScriptRoot

Register-EventScript -eventToRegister "Startup" -pathToScript "$currentLocation\ScriptToRun.ps1" -scriptParameters "Startup"
Register-EventScript -eventToRegister "Shutdown" -pathToScript "$currentLocation\ScriptToRun.ps1" -scriptParameters "Shutdown"

@jeremiassamuelzitnik
Copy link

And this is for unregister:

function Unregister-EventScript {
    param (
        [string] $eventToUnregister # Either Startup or Shutdown
    )

    # Base registry paths where scripts are registered
    $baseRegPaths = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\${eventToUnregister}",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\${eventToUnregister}"
    )

    # Collect all registered scripts
    $scripts = @()
    foreach ($basePath in $baseRegPaths) {
        if (Test-Path $basePath) {
            Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue | ForEach-Object {
                $index = $_.PSChildName
                $scriptPath = (Get-ItemProperty -Path "$basePath\$index\0" -ErrorAction SilentlyContinue).Script
                $params     = (Get-ItemProperty -Path "$basePath\$index\0" -ErrorAction SilentlyContinue).Parameters

                if ($scriptPath) {
                    $scripts += [PSCustomObject]@{
                        Event      = $eventToUnregister
                        Index      = $index
                        ScriptPath = $scriptPath
                        Parameters = $params
                        RegBase    = $basePath
                    }
                }
            }
        }
    }

    if (-not $scripts) {
        Write-Host "No scripts found for ${eventToUnregister}"
        return
    }

    # Show scripts and ask the user which ones to remove
    Write-Host "`nScripts found for event ${eventToUnregister}:`n"
    $i = 0
    $scripts | ForEach-Object {
        Write-Host "[$i] $($_.ScriptPath) $($_.Parameters)"
        $i++
    }

    $selection = Read-Host "Enter the number of the script to remove (comma-separated for multiple, or 'all' for all)"
    if ($selection -eq "all") {
        $toRemove = $scripts
    }
    else {
        $indexes = $selection -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' }
        $toRemove = $scripts[$indexes]
    }

    # Remove selected scripts
    foreach ($s in $toRemove) {
        $regPath = "$($s.RegBase)\$($s.Index)"
        if (Test-Path $regPath) {
            Remove-Item -Path $regPath -Recurse -Force
            Write-Host "Removed: $($s.ScriptPath) [${eventToUnregister}]"
        }
    }
}

# Example usage:
Unregister-EventScript -eventToUnregister "Startup"
Unregister-EventScript -eventToUnregister "Shutdown"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment