Create-IntuneRemediation.ps1

<#PSScriptInfo
 
.VERSION 2.0.3
 
.GUID 48aff0d5-53e4-4b50-aec8-436c2e9fc46b
 
.AUTHOR Jeff Gilbert
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS Intune
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
    02.12.26 v1.0.0 Added a new task to generate an example detection and remediation script pair. Updated the example scripts to include a detection and remediation for Notepad++ installation.
    04.03.26 v1.1.0 Updated log cleanup logic to remove ERR logs older than 14 days.
    06.06.26 v1.1.1 Updated detection output to use a generic list for better performance and compatibility.
    08.07.26 v1.2.0 Added registry tracking information.
    10.1.26 v1.2.1 Updated logging and registry checkpoint logic settings for better user experience.
    10.2.26 v1.2.2 Updated interactive logging and registry tracking logic for improved debugging and user experience.
    10.17.26 v2.0.0 Major update with new troubleshooting features and script improvements.
    10.19.26 v2.0.2 Minor updates to comment-based help and bug fixes.
    06.06.26 v2.0.3 Added source script version to generated script files.
#>


<#
 
.SYNOPSIS
    Creates a VS Code workspace for building and testing Intune remediation scripts.
 
.DESCRIPTION
    Create-IntuneRemediation scaffolds a local VS Code Workspace project structure for Microsoft Intune remediation development.
    It creates a dedicated workspace folder under your Documents directory, sets up a standard scripts folder and scratch testing area, and generates a Visual Studio Code workspace with ready-to-use tasks for creating example files, troubleshooting remediation issues, downloading helper scripts, and running PowerShell analysis.
    This script is designed to save time for administrators and engineers who want a consistent starting point for authoring, validating, and troubleshooting Intune detection and remediation scripts without manually setting up the project structure each time.
    The generated workspace includes example templates and workflow automation to help you get productive quickly.
 
.PARAMETER outDir
    The parent folder where the remediation workspace will be created. By default, this is a folder named "IntuneRemediations" under your Documents directory.
 
.PARAMETER workSpaceName
    The name of the generated VS Code workspace. The default is "MyRemediation".
 
.EXAMPLE
    .\Create-IntuneRemediation.ps1 -outDir "IntuneRemediations" -workSpaceName "MyRemediation"
 
    Creates a new Intune remediation development workspace named "MyRemediation" in the Documents\IntuneRemediations folder.
 
.PARAMETER outDir
    The output directory where the workspace and files will be created. Default is the location the IntuneRemediations folder in the My Documents directory.
 
.PARAMETER workSpaceName
    The name of the workspace to be created. Default is "MyRemediation".
 
.EXAMPLE
    .\Create-IntuneRemediation.ps1 -outDir "IntuneRemediations" -workSpaceName "MyRemediation"
    Creates a workspace named "MyRemediation" in the "Documents\IntuneRemediations" directory.
 
.NOTES
    The script creates a reusable workspace structure and helper files to support your remediation development workflow.
    A scratch folder is included for temporary files and testing.
 
#>


param (
    [Parameter(Mandatory=$false)] [string]$workSpaceName = "MyRemediation",
    [Parameter(Mandatory=$false)] [string]$outDir = "IntuneRemediations"
)

$version = "2.0.3"
Clear-Host
Write-Output "Running Intune Remediation Scripts Workspace Creator v$version ..."
Write-Host `n

#region Workspace and VS Code Setup
$myDocs = [Environment]::GetFolderPath("MyDocuments")
$outDir = Join-Path $myDocs $outDir
$path = Join-Path $outDir $workSpaceName
    if (-not (Test-Path -Path $path)) {
        New-Item -Path $path -ItemType Directory | Out-Null
    } else {
        $rand = Get-Random -Minimum 1 -Maximum 100
        $workSpaceName = $workSpaceName + "-" + $rand
        $path = Join-Path $outDir $workSpaceName
    }

# Create the scripts and scratch folders
New-Item -ItemType Directory -Path $path\scripts | Out-Null
New-Item -ItemType Directory -Path $path\scratch | Out-Null

# Define the folders to include in the workspace
$folders = @(
    @{
        path = $path
    }
)
# Define workspace settings (optional)
$settings = @{
    "editor.tabSize" = 4
    "files.exclude" = @{
        "*.code-workspace" = $true
        ".vscode" = $true
        ".git" = $true
    }
}

$tasks = @{
    version = "2.0.0"
    tasks = @(
        @{        
            label = "Generate example files"
            type = "shell"
            command = "./.vscode/Generate-Examples.ps1"
            problemMatcher = "[]"
        }
        @{        
            label = "Show me how to troubleshoot remediations"
            type = "shell"
            command = "./.vscode/TaskHelp.ps1"
            problemMatcher = "[]"
        }
        @{        
            label = "Download Troubleshooting Script"
            type = "shell"
            command = "./.vscode/TaskTsScript.ps1"
            problemMatcher = "[]"
        }
        @{        
            label = "Analyze Script"
            type = "shell"
            command = "Invoke-ScriptAnalyzer scripts/*.ps1"
            problemMatcher = "[]"
        }
    )
}

# Create the workspace JSON structure
$workspace = @{
    folders = $folders
    settings = $settings
    tasks = $tasks
}
# Convert the workspace structure to JSON
$workspaceJson = $workspace | ConvertTo-Json -Depth 10 -Compress

# Write the JSON to the .code-workspace file
Write-Output "Creating the $workSpaceName VS Code Workspace:"

$workspaceFilePath = Join-Path $path "$workSpaceName.code-workspace"
Set-Content -Path $workspaceFilePath -Value $workspaceJson -Encoding UTF8 | Out-Null
Write-Host " WORKSPACE: $workSpaceName.code-workspace created at $outDir"

# Put shortcut in outdir
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$outDir\$workSpaceName.lnk")
$Shortcut.TargetPath = "$workspaceFilePath"
$Shortcut.Save()

$taskFolderPath = Join-Path $path ".vscode"
# Ensure the .vscode folder exists
    if (-not (Test-Path -Path $taskFolderPath)) {
        New-Item -ItemType Directory -Path $taskFolderPath | Out-Null
        $hide = Get-Item $taskFolderPath -Force
        $hide.attributes = 'Hidden' 
    }

#endregion Workspace and VS Code Setup

#region Detection Script Creation
# -------------------------------------------- Create the detection.ps1 file -------------------------------------------
$detectionScript = @"
<#
.SYNOPSIS
    Intune Remediation detection script template.
 
.DESCRIPTION
    This script template provides a starting point for creating Intune Remediation scripts.
 
    The script includes helper functions for:
      - CMTrace-style logging
      - 64-bit PowerShell relaunch
      - Detection result tracking in the registry
      - Automatic cleanup of stale remediation logs
 
.NOTES
    Detection results are stored under:
        HKLM:\SOFTWARE\IntuneRemediations\<LogName>
 
    Log File: (deleted if remediation script isn't triggered)
        <SystemDrive>\Logs\IR\<LogName>.log
 
    Detection output written with the -StdOut switch is displayed in the
    Intune admin center.
 
    Version: 1.0.0
    Author: Jeff Gilbert
 
.EXIT CODES
    0 = Compliant
    1 = Non-compliant (remediation required)
    2 = Error encountered
#>
 
#region Logging
# Template logging function for the Intune detection script
# * DO NOT MODIFY *
function Write-Log {
 
    #-- THIS FUNCTION REQUIRES THE FOLLOWING BE DECLARED IN THE MAIN SCRIPT --#
    # `$logPath
    # `$logName
    # `$detectionOutput = [System.Collections.Generic.List[string]]::new()
    #-------------------------------------------------------------------------#
 
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = `$true, Position = 0)] [ValidateNotNullOrEmpty()] [string] `$Message,
        [Parameter(Mandatory = `$false, Position = 1)] [string] `$Component = "Detection",
        [Parameter(Mandatory = `$false, Position = 2)] [ValidateSet(1, 2, 3)] [int] `$Type = 1,
        [Parameter(Mandatory = `$false)] [switch] `$StdOut
    )
  # Capture once to avoid drift between Time/Date
    `$now = Get-Date
    `$Time = `$now.ToString('HH:mm:ss.ffffff', [System.Globalization.CultureInfo]::InvariantCulture)
    `$Date = `$now.ToString('MM-dd-yyyy', [System.Globalization.CultureInfo]::InvariantCulture)
  # Escape message for CMTrace payload (XML-like). SecurityElement.Escape handles &, <, >, " (quotes) safely
    `$escapedMessage = [System.Security.SecurityElement]::Escape(`$Message).Replace('&apos;', "'")
  # Identify if it was a function that called Log and make it the component"
    `$scriptFullPath = `$PSCommandPath ; `$scriptName = Split-Path -Path `$scriptFullPath -Leaf
    `$caller = (Get-PSCallStack)[1].Command
    if (-not (`$caller -eq `$scriptName)){ `$Component = `$caller }
  # Build CMTrace entry (keep empty attributes as per CMTrace spec)
    `$logLine = ('<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="" type="{4}" thread="" file="">' ``
                -f `$escapedMessage, `$Time, `$Date, `$Component, `$Type)
    try {
      # Write the log entry
        `$dir = Split-Path -Path `$logFile -Parent
      # Ensure log directory exists
        New-Item -ItemType Directory -Path `$dir -Force | Out-Null
      # Write message to the log file
        `$logLine | Out-File -LiteralPath `$logFile -Append -Encoding UTF8
      # Add message to detection results sent to the Intune admin center
        if (`$StdOut) { `$detectionOutput.Add(`$Message) }
      # Write all log lines to console for interactive testing/transcript capture
        if (`$InteractiveLogging) {
            switch (`$Type) {
                1 { Write-Host `$Message -ForegroundColor Gray }
                2 { Write-Host `$Message -ForegroundColor Yellow }
                3 { Write-Host `$Message -ForegroundColor Red }
                default { Write-Host `$Message }
            }
        }
    }
    catch { # If writing fails write error
        Write-Error -Message ("Failed to write log to '{0}': {1}" -f `$logFile, `$_.Exception.Message)
        return
    }
}
#endregion Logging
 
#region Template Helpers
# Template helper functions for the Intune detection script
# * DO NOT MODIFY *
function Restart-64Bit {
    # If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process first
    if ("`$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") {
        if (Test-Path "`$(`$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") {
            & "`$(`$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "`$PSCommandPath"
            Exit `$lastexitcode
            }
        }
}
 
function Detection-Result {
    param(
        [string]`$logName,
        [string]`$ScriptVersion,
        [datetime]`$StartTime,
        [datetime]`$EndTime,
        [string]`$exitCode
    )
 
    try{
        if (-not (Test-Path `$BaseKey)) { New-Item -Path `$BaseKey -Force | Out-Null }
        `$DurationSeconds = [math]::Round(((`$EndTime).ToUniversalTime() -
        (`$StartTime).ToUniversalTime()).TotalSeconds, 2)
 
        if (`$exitCode -ne 0) {
            `$DetectionResult = "Fail"
            `$details = (`$detectionOutput -join " | ")
        } else {
            `$DetectionResult = "Pass"
            `$details = "[PASS] All checks successful"
        }
 
        `$Properties = @{
            DetectionValue = `$DetectionResult
            DetectionEndTimeUtc = `$EndTime.ToUniversalTime().ToString("o")
            DetectionRuntimeSeconds = `$DurationSeconds
            DetectionBeginTimeUtc = `$StartTime.ToUniversalTime().ToString("o")
            DetectionState = `$details
            DetectionScriptVersion = `$ScriptVersion
            DetectionScriptExitCode = `$exitCode
        }
 
        foreach (`$Property in `$Properties.GetEnumerator()) {
            New-ItemProperty ```
                -Path `$BaseKey ```
                -Name `$Property.Key ```
                -Value `$Property.Value ```
                -PropertyType String ```
                -Force | Out-Null
        }
     
        Write-Log "***** End of detection script actions *****"
    }
    catch {
        Write-Log "Failed to write detection registry result: `$(`$_.Exception.Message)" -Type 2
        Write-Log "***** End of detection script actions *****"
        `$exitCode = 2
    }
}
 
function Remove-ExpiredRemediationLog {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateRange(0, 36500)]
        [int] `$RetainDays = 30
    )
 
    if (`$exitCode -eq 1) { # Do not delete the log
        return
    }
         
    `$BaseKey = Join-Path -Path `$StateKey -ChildPath `$logName
 
    if (-not (Test-Path -LiteralPath `$BaseKey)) {
        Remove-Item -LiteralPath `$LogFile -Force
        return
    }
 
    `$keyProperties = Get-ItemProperty -LiteralPath `$BaseKey
    `$resultProperty = `$keyProperties.PSObject.Properties['RemediationResult']
    if (`$null -eq `$resultProperty) { # No remediation was recorded, so remove the unnecessary log.
        if (Test-Path -LiteralPath `$LogFile) {
            if (`$PSCmdlet.ShouldProcess(`$LogFile, 'Delete log with no remediation result')) {
                Remove-Item -LiteralPath `$LogFile -Force
            }
        }
        return
    } else { # A remediation has occured, let's see how long ago.
        `$lastRunTime = Get-ItemPropertyValue -LiteralPath `$BaseKey ``
            -Name 'RemediationBeginTimeUtc' -ErrorAction SilentlyContinue
 
        `$lastRun = [DateTimeOffset]::MinValue
        if ([string]::IsNullOrWhiteSpace(`$lastRunTime) -or
            -not [DateTimeOffset]::TryParse(
                `$lastRunTime,
                [Globalization.CultureInfo]::InvariantCulture,
                [Globalization.DateTimeStyles]::None,
                [ref]`$lastRun
            )) {
            Write-Warning "RemediationBeginTimeUtc is missing or invalid in `$BaseKey; keeping the log."
            return
        }
        `$cutOff = [DateTimeOffset]::UtcNow.AddDays(-`$RetainDays)
        if (`$lastRun -lt `$cutOff -and (Test-Path -LiteralPath `$LogFile)) {
            if (`$PSCmdlet.ShouldProcess(`$LogFile, "Delete log older than `$RetainDays days")) {
                Remove-Item -LiteralPath `$LogFile -Force
            }
        }
    }
}
#endregion Template Helpers
 
#region Your Helpers
 
# Your helper functions for the Intune detection script go here
 
#endregion Your Helpers
 
# Script generated using Create-IntuneRemediation v$version
 
#---------------------------------------------- BEGIN ----------------------------------------------|
 
# --- Script Configuration ---
# * MODIFY THESE AS NEEDED *
`$ScriptVersion = "1.0.0" # Version of the script
`$logPath = "`$env:SystemDrive\Logs\IR" # Ensure this path is pulled with Intune collects diagnostics
`$logName = "$workSpaceName" # Name of the log file
`$StateKey = "HKLM:\SOFTWARE\IntuneRemediations" # Base registry key for storing remediation state
`$InteractiveLogging = `$false # Enable/disable display console output for troubleshooting (DO NOT deploy as `$true)
`$verboseLogging = `$false # Enable/disable script transcript for troubleshooting
`$retainDays = 30 # How long to retain logs after a successful remediation has run (default is 30 days)
# -------------------------------------
 
#region Do Not Modify
# * DO NOT MODIFY THESE *
`$ErrorActionPreference = 'Stop' # Ensures try/catch works reliably [do not modify]
`$logFile = Join-Path `$logPath "`$logName.log" # Full path to the log file [do not modify]
`$baseKey = Join-Path `$StateKey "`$logName" # Base registry key for storing remediation state [do not modify]
`$exitCode = 0 # Intitalize exit code for compliance [do not modify]
`$StartTime = Get-Date # Record script execution start time [do not modify]
`$detectionOutput = [System.Collections.Generic.List[string]]::new() # Detection output to display in Intune admin center [do not modify]
Restart-64Bit # Ensure we are running as a 64-bit process on an x64 system [do not modify]
if (`$verboseLogging) { Start-Transcript -Path (Join-Path `$logPath "`${logName}_verbose.log") -Append }
Write-Log "***** Starting detection script actions. *****" -Component 'Detection Begin'
Write-Log "Detection script version: `$ScriptVersion" -Component 'Version'
# -------------------------------------
#endregion Do Not Modify
 
try {
    <# INSTRUCTIONS:
        - Only modify the detection content within this Try block!
        - You only need to check for failures. If checks fail, set `$exitCode = 1. If script fails, set `$exitCode = 2.
        - Use Write-Log to output messages to the log (also ouputs to the console if `$InteractiveLogging is `$true).
        If you want to include the output in the Intune admin center's detection output, add -StdOut
        to the end of the line. Max length of all aggregated messages is 2,048 characters total.
 
    # ----------------------------------------------------
    # --------- Your detection logic starts here ---------
    # --------------------------------------------------#>
 
 
 
 
 
    # ----------------------------------------------------
    # ---------- End of your detection logic ----------
    # ----------------------------------------------------
     
  # Compliance Result * DO NOT MODIFY *
    if (`$exitCode -eq 0) { # Log will not be saved if there are no issues to log and the device hasn't been non-compliant recently.
        Write-Log "[PASS] The device is compliant." -StdOut # Intune detection output
        exit `$exitCode
    } elseif (`$exitcode -eq 2){
        Write-Log "[FAIL] The device is non-compliant due to a script failure." -Type 3 -StdOut
        exit `$exitCode
    } else { # Device is not compliant
        Write-Log "[FAIL] The device is non-compliant."
        exit `$exitCode
    }
} catch { # * DO NOT MODIFY *
    `$exitCode = 2
    Write-Log "[ERROR] `$(`$_.Exception.Message)" -Type 3 -StdOut
    exit `$exitCode
} finally { # * DO NOT MODIFY *
    `$EndTime = Get-Date
  # Create registry checkpoint for detection results
    Detection-Result -logName `$logName -ScriptVersion `$ScriptVersion -StartTime `$StartTime -EndTime `$EndTime -exitCode `$exitCode
  # Delete existing remediation logs for issues that have been remediated longer than `$retainDays ago.
    Remove-ExpiredRemediationLog -RetainDays `$retainDays
    Write-Output (`$detectionOutput -join " | ") # Intune detection output
    if (`$verboseLogging) { Stop-Transcript }
}
 
"@

$detectionScript | Out-File -FilePath $path\scripts\$workSpaceName"_detection.ps1" -Encoding UTF8 -Force
Write-Output " TEMPLATE: Detection script created"
#endregion Detection Script Creation

#region Remediation Script Creation
#--------------------------------------------- Create Example File --------------------------------------------
$remediationScript  = @"
<#
.SYNOPSIS
    Standard Intune Remediation Script Template with CMTrace-compatible logging
    and registry checkpoint reporting.
 
.DESCRIPTION
    This script template provides a starting point for creating Intune Remediation scripts.
 
.FEATURES
    - CMTrace-compatible logging
    - Automatic relaunch in 64-bit PowerShell when required
    - Remediation run counter
    - Remediation result tracking in the registry
    - Transcript logging support for troubleshooting
 
.NOTES
    Registry Location:
        HKLM:\SOFTWARE\IntuneRemediations\<LogName>
 
    Log File:
        <SystemDrive>\Logs\IR\<LogName>.log
 
    Recorded Metadata:
        - RunId (correlation identifier)
        - RunCount
        - Remediation result
        - Runtime duration
        - Remediation timestamps
        - Last detection failure details
        - Script version
 
    Version: 1.0.0
    Author: Jeff Gilbert
 
.EXIT CODES
    0 = Remediation successful
    1 = Remediation failed
    2 = Error encountered
 
#>
 
#region Logging
# Template logging function for the Intune detection script
# * DO NOT MODIFY *
function Write-Log {
 
    #-- THIS FUNCTION REQUIRES THE FOLLOWING BE DECLARED IN THE MAIN SCRIPT --#
    # `$logPath
    # `$logName
    # `$detectionOutput = [System.Collections.Generic.List[string]]::new()
    #-------------------------------------------------------------------------#
 
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = `$true, Position = 0)] [ValidateNotNullOrEmpty()] [string] `$Message,
        [Parameter(Mandatory = `$false, Position = 1)] [string] `$Component = "Remediation",
        [Parameter(Mandatory = `$false, Position = 2)] [ValidateSet(1, 2, 3)] [int] `$Type = 1,
        [Parameter(Mandatory = `$false)] [switch] `$StdOut
    )
  # Capture once to avoid drift between Time/Date
    `$now = Get-Date
    `$Time = `$now.ToString('HH:mm:ss.ffffff', [System.Globalization.CultureInfo]::InvariantCulture)
    `$Date = `$now.ToString('MM-dd-yyyy', [System.Globalization.CultureInfo]::InvariantCulture)
  # Escape message for CMTrace payload (XML-like). SecurityElement.Escape handles &, <, >, " (quotes) safely
    `$escapedMessage = [System.Security.SecurityElement]::Escape(`$Message).Replace('&apos;', "'")
  # Identify if it was a function that called Log and make it the component"
    `$scriptFullPath = `$PSCommandPath ; `$scriptName = Split-Path -Path `$scriptFullPath -Leaf
    `$caller = (Get-PSCallStack)[1].Command
    if (-not (`$caller -eq `$scriptName)){ `$Component = `$caller }
  # Build CMTrace entry (keep empty attributes as per CMTrace spec)
    `$logLine = ('<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="" type="{4}" thread="" file="">' ``
                -f `$escapedMessage, `$Time, `$Date, `$Component, `$Type)
    try {
      # Write the log entry
        `$dir = Split-Path -Path `$logFile -Parent
      # Ensure log directory exists
        New-Item -ItemType Directory -Path `$dir -Force | Out-Null
      # Write message to the log file
        `$logLine | Out-File -LiteralPath `$logFile -Append -Encoding UTF8
      # Add message to detection results sent to the Intune admin center
        if (`$StdOut) { `$detectionOutput.Add(`$Message) }
      # Write all log lines to console for interactive testing/transcript capture
        if (`$InteractiveLogging) {
            switch (`$Type) {
                1 { Write-Host `$Message -ForegroundColor Gray }
                2 { Write-Host `$Message -ForegroundColor Yellow }
                3 { Write-Host `$Message -ForegroundColor Red }
                default { Write-Host `$Message }
            }
        }
    }
    catch { # If writing fails write error
        Write-Error -Message ("Failed to write log to '{0}': {1}" -f `$logFile, `$_.Exception.Message)
        return
    }
}
#endregion Logging
 
#region Helpers
function Restart-64Bit {
    # If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process first
    if ("`$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") {
        if (Test-Path "`$(`$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") {
            & "`$(`$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "`$PSCommandPath"
            Exit `$lastexitcode
            }
        }
}
 
function Increment-RemediationRunCount {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]`$LogName
    )
 
    try {
 
        if (-not (Test-Path -LiteralPath `$baseKey)) { New-Item -Path `$baseKey -Force | Out-Null }
         
        `$existing = Get-ItemProperty -Path `$baseKey -Name "RunCount" -ErrorAction SilentlyContinue
        `$currentCount = if (`$null -ne `$existing) { [int]`$existing.RunCount } else { 0 }
 
        `$newCount = `$currentCount + 1
 
        New-ItemProperty ``
            -Path `$baseKey ``
            -Name "RunCount" ``
            -Value `$newCount ``
            -PropertyType DWord ``
            -Force | Out-Null
 
        return `$newCount
    }
    catch {
        Write-Log "Failed to increment remediation run count: `$(`$_.Exception.Message)" -Type 2
        return 0
    }
}
 
function Remediation-Result {
    param(
        [string]`$LogName,
        [string]`$ScriptVersion,
        [string]`$RunId,
        [string]`$LastRemediationResult,
        [datetime]`$StartTime,
        [datetime]`$EndTime = (Get-Date),
        [string]`$Details = "",
        [string]`$exitCode
    )
 
    try {
        if (-not (Test-Path `$baseKey)) { New-Item -Path `$baseKey -Force | Out-Null }
        `$DurationSeconds = [math]::Round(((`$EndTime).ToUniversalTime() -
        (`$StartTime).ToUniversalTime()).TotalSeconds, 2)
 
        `$LastDetectionFailure = (Get-ItemProperty -Path `$baseKey -Name "DetectionState" -ErrorAction SilentlyContinue).DetectionState
 
        if (`$exitCode -ne 0) {
            `$RemediationResult = "Fail"
            `$RemediationDetails = (`$detectionOutput -join " | ")
        } else {
            `$RemediationResult = "Pass"
            `$RemediationDetails = "[PASS] All checks successful"
        }
 
        `$properties = [ordered]@{
            LastDetectionFailure = `$LastDetectionFailure
            RemediationDetails = `$RemediationDetails
            RemediationResult = `$RemediationResult
            RemediationBeginTimeUtc = `$StartTime.ToUniversalTime().ToString("o")
            RemediationEndTimeUtc = `$EndTime.ToUniversalTime().ToString("o")
            RemediationRuntimeSeconds = `$DurationSeconds.ToString()
            RunId = `$RunId
            RemediationScriptVersion = `$ScriptVersion
            RemediationScriptExitCode = `$exitCode
        }
 
        foreach (`$property in `$properties.GetEnumerator()) {
            New-ItemProperty ``
                -Path `$baseKey ``
                -Name `$property.Key ``
                -Value `$property.Value ``
                -PropertyType String ``
                -Force | Out-Null
        }
 
        New-ItemProperty ``
            -Path `$baseKey ``
            -Name "RunCount" ``
            -Value ([int]`$script:RunCount) ``
            -PropertyType DWord ``
            -Force | Out-Null
     
        Write-Log "Registry checkpoint updated: `$baseKey"
        Write-Log "***** End of remediation script actions. *****"
    }
    catch {
        `$exitCode = 2
        Write-Log "Failed to write remediation registry result: `$(`$_.Exception.Message)" -Type 2
        Write-Log "***** End of remediation script actions. *****"
    }
}
 
#endregion Helpers
 
#region Your Helpers
 
# Your helper functions for the Intune remediation script go here
 
#endregion Your Helpers
 
# Script generated using Create-IntuneRemediation v$version
 
#---------------------------------------------- BEGIN ----------------------------------------------|
# --- Script Configuration ---
# * MODIFY THESE AS NEEDED *
`$scriptVersion = "1.0.0" # Script version
`$logPath = "`$env:SystemDrive\Logs\IR" # Log path must match the path in the detection script
`$logName = "$workSpaceName" # Log name must match the name in the detection script
`$stateKey = "HKLM:\SOFTWARE\IntuneRemediations" # Regkey must match the regkey in the detection script
`$interactiveLogging = `$false # Display console output for troubleshooting
`$verboseLogging = `$false # Enables script transcript for troubleshooting
# -------------------------------------
 
#region Do Not Modify
# * DO NOT MODIFY THESE *
`$ErrorActionPreference = 'Stop' # Ensures try/catch works reliably [do not modify]
`$logFile = Join-Path `$logPath "`$logName.log" # Full path to the log file [do not modify]
`$baseKey = Join-Path `$stateKey "`$logName" # Base registry key for storing remediation state [do not modify]
`$exitCode = 0 # Intitalize exit code for compliance [do not modify]
`$RunId = [guid]::NewGuid().ToString() # Generates a new Correlation Id (CID) on every run [do not modify]
`$detectionOutput = [System.Collections.Generic.List[string]]::new() # Detection output to display in Intune admin center [do not modify]
`$StartTime = Get-Date # Record script execution start time [do not modify]
Restart-64Bit # Ensure we are running as a 64-bit process on an x64 system [do not modify]
`$RunCount = Increment-RemediationRunCount -LogName `$logName # Keep track of how many times this script has run [do not modify]
if (`$verboseLogging) { Start-Transcript -Path (Join-Path `$logPath "`${logName}_verbose.log") -Append }
Write-Log "***** Starting remediation script actions. *****"
Write-Log "Remediation script version: `$scriptVersion." -Component "Version"
Write-Log "RunId: `$RunId"
Write-Log "Remediation Run Count: `$RunCount"
# -------------------------------------
#endregion Do Not Modify
 
try {
 
    <# INSTRUCTIONS:
    For any remediation failures:
     - Set `$exitCode = 1 for remediation failures.
     - Set `$exitCode = 2 for script failures.
     - Add -StdOut at the end of the Write-Log commands to append issues in the registry checkpoint RemediationDetails value
     
    # ----------------------------------------------------
    # --------- Your remediation logic goes here ---------
    # --------------------------------------------------#>
 
     
 
 
 
    # ----------------------------------------------------
    # --------- End of your remediation logic ---------
    # ----------------------------------------------------
 
} catch { # * DO NOT MODIFY *
    `$exitCode = 2
    Write-Log "[ERROR] `$(`$_.Exception.Message)" -Type 3 -StdOut
    Write-Log "***** End of remediation script actions. *****"
} finally { # * DO NOT MODIFY *
   `$EndTime = Get-Date
  # Create registry checkpoint for remediation results
    Remediation-Result -logName `$logName -ScriptVersion `$ScriptVersion -StartTime `$StartTime -EndTime `$EndTime -RunId `$RunId -exitCode `$exitCode
    if (`$verboseLogging) { Stop-Transcript }
}
"@

$remediationScript | Out-File -FilePath $path\scripts\$workSpaceName"_remediation.ps1" -Encoding UTF8 -Force
Write-Output " TEMPLATE: Remediation script created"

#endregion Remediation Script Creation

#region Example Detection Script Creation
#--------------------------------------------- Create Example File --------------------------------------------
$exampleDet = @"
`$makeExampleDet = @"
<#
.SYNOPSIS
    Intune Remediation detection script template.
 
.DESCRIPTION
    This script template provides a starting point for creating Intune Remediation scripts.
 
    The script includes helper functions for:
      - CMTrace-style logging
      - 64-bit PowerShell relaunch
      - Detection result tracking in the registry
      - Automatic cleanup of stale remediation logs
 
.NOTES
    Detection results are stored under:
        HKLM:\SOFTWARE\IntuneRemediations\<LogName>
 
    Log File: (deleted if remediation script isn't triggered)
        <SystemDrive>\Logs\IR\<LogName>.log
 
    Detection output written with the -StdOut switch is displayed in the
    Intune admin center.
 
    Version: 1.0.0
    Author: Jeff Gilbert
 
.EXIT CODES
    0 = Compliant
    1 = Non-compliant (remediation required)
    2 = Error encountered
#>
 
#region Logging
# Template logging function for the Intune detection script
# * DO NOT MODIFY *
function Write-Log {
 
    #-- THIS FUNCTION REQUIRES THE FOLLOWING BE DECLARED IN THE MAIN SCRIPT --#
    # ```$logPath
    # ```$logName
    # ```$detectionOutput = [System.Collections.Generic.List[string]]::new()
    #-------------------------------------------------------------------------#
 
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = ```$true, Position = 0)] [ValidateNotNullOrEmpty()] [string] ```$Message,
        [Parameter(Mandatory = ```$false, Position = 1)] [string] ```$Component = "Detection",
        [Parameter(Mandatory = ```$false, Position = 2)] [ValidateSet(1, 2, 3)] [int] ```$Type = 1,
        [Parameter(Mandatory = ```$false)] [switch] ```$StdOut
    )
  # Capture once to avoid drift between Time/Date
    ```$now = Get-Date
    ```$Time = ```$now.ToString('HH:mm:ss.ffffff', [System.Globalization.CultureInfo]::InvariantCulture)
    ```$Date = ```$now.ToString('MM-dd-yyyy', [System.Globalization.CultureInfo]::InvariantCulture)
  # Escape message for CMTrace payload (XML-like). SecurityElement.Escape handles &, <, >, " (quotes) safely
    ```$escapedMessage = [System.Security.SecurityElement]::Escape(```$Message).Replace('&apos;', "'")
  # Identify if it was a function that called Log and make it the component"
    ```$scriptFullPath = ```$PSCommandPath ; ```$scriptName = Split-Path -Path ```$scriptFullPath -Leaf
    ```$caller = (Get-PSCallStack)[1].Command
    if (-not (```$caller -eq ```$scriptName)){ ```$Component = ```$caller }
  # Build CMTrace entry (keep empty attributes as per CMTrace spec)
    ```$logLine = ('<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="" type="{4}" thread="" file="">' ````
                -f ```$escapedMessage, ```$Time, ```$Date, ```$Component, ```$Type)
    try {
      # Write the log entry
        ```$dir = Split-Path -Path ```$logFile -Parent
      # Ensure log directory exists
        New-Item -ItemType Directory -Path ```$dir -Force | Out-Null
      # Write message to the log file
        ```$logLine | Out-File -LiteralPath ```$logFile -Append -Encoding UTF8
      # Add message to detection results sent to the Intune admin center
        if (```$StdOut) { ```$detectionOutput.Add(```$Message) }
      # Write all log lines to console for interactive testing/transcript capture
        if (```$InteractiveLogging) {
            switch (```$Type) {
                1 { Write-Host ```$Message -ForegroundColor Gray }
                2 { Write-Host ```$Message -ForegroundColor Yellow }
                3 { Write-Host ```$Message -ForegroundColor Red }
                default { Write-Host ```$Message }
            }
        }
    }
    catch { # If writing fails write error
        Write-Error -Message ("Failed to write log to '{0}': {1}" -f ```$logFile, ```$_.Exception.Message)
        return
    }
}
#endregion Logging
 
#region Template Helpers
# Template helper functions for the Intune detection script
# * DO NOT MODIFY *
function Restart-64Bit {
    # If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process first
    if ("```$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") {
        if (Test-Path "```$(```$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") {
            & "```$(```$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "```$PSCommandPath"
            Exit ```$lastexitcode
            }
        }
}
 
function Detection-Result {
    param(
        [string]```$logName,
        [string]```$ScriptVersion,
        [datetime]```$StartTime,
        [datetime]```$EndTime,
        [string]```$exitCode
    )
 
    try{
        if (-not (Test-Path ```$BaseKey)) { New-Item -Path ```$BaseKey -Force | Out-Null }
        ```$DurationSeconds = [math]::Round(((```$EndTime).ToUniversalTime() -
        (```$StartTime).ToUniversalTime()).TotalSeconds, 2)
 
        if (```$exitCode -ne 0) {
            ```$DetectionResult = "Fail"
            ```$details = (```$detectionOutput -join " | ")
        } else {
            ```$DetectionResult = "Pass"
            ```$details = "[PASS] All checks successful"
        }
 
        ```$Properties = @{
            DetectionValue = ```$DetectionResult
            DetectionEndTimeUtc = ```$EndTime.ToUniversalTime().ToString("o")
            DetectionRuntimeSeconds = ```$DurationSeconds
            DetectionBeginTimeUtc = ```$StartTime.ToUniversalTime().ToString("o")
            DetectionState = ```$details
            DetectionScriptVersion = ```$ScriptVersion
            DetectionScriptExitCode = ```$exitCode
        }
 
        foreach (```$Property in ```$Properties.GetEnumerator()) {
            New-ItemProperty ````
                -Path ```$BaseKey ````
                -Name ```$Property.Key ````
                -Value ```$Property.Value ````
                -PropertyType String ````
                -Force | Out-Null
        }
     
        Write-Log "***** End of detection script actions *****"
    }
    catch {
        Write-Log "Failed to write detection registry result: ```$(```$_.Exception.Message)" -Type 2
        Write-Log "***** End of detection script actions *****"
        ```$exitCode = 2
    }
}
 
function Remove-ExpiredRemediationLog {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateRange(0, 36500)]
        [int] ```$RetainDays = 30
    )
 
    if (```$exitCode -eq 1) { # Do not delete the log
        return
    }
         
    ```$BaseKey = Join-Path -Path ```$StateKey -ChildPath ```$logName
    if (-not (Test-Path -LiteralPath ```$BaseKey)) {
        Remove-Item -LiteralPath ```$LogFile -Force
        return
    }
 
    ```$keyProperties = Get-ItemProperty -LiteralPath ```$BaseKey
    ```$resultProperty = ```$keyProperties.PSObject.Properties['RemediationResult']
    if (```$null -eq ```$resultProperty) { # No remediation was recorded, so remove the unnecessary log.
        if (Test-Path -LiteralPath ```$LogFile) {
            if (```$PSCmdlet.ShouldProcess(```$LogFile, 'Delete log with no remediation result')) {
                Remove-Item -LiteralPath ```$LogFile -Force
            }
        }
        return
    } else { # A remediation has occured, let's see how long ago.
        ```$lastRunTime = Get-ItemPropertyValue -LiteralPath ```$BaseKey ````
            -Name 'RemediationBeginTimeUtc' -ErrorAction SilentlyContinue
 
        ```$lastRun = [DateTimeOffset]::MinValue
        if ([string]::IsNullOrWhiteSpace(```$lastRunTime) -or
            -not [DateTimeOffset]::TryParse(
                ```$lastRunTime,
                [Globalization.CultureInfo]::InvariantCulture,
                [Globalization.DateTimeStyles]::None,
                [ref]```$lastRun
            )) {
            Write-Warning "RemediationBeginTimeUtc is missing or invalid in ```$BaseKey; keeping the log."
            return
        }
        ```$cutOff = [DateTimeOffset]::UtcNow.AddDays(-```$RetainDays)
        if (```$lastRun -lt ```$cutOff -and (Test-Path -LiteralPath ```$LogFile)) {
            if (```$PSCmdlet.ShouldProcess(```$LogFile, "Delete log older than ```$RetainDays days")) {
                Remove-Item -LiteralPath ```$LogFile -Force
            }
        }
    }
}
#endregion Template Helpers
 
#region Your Helpers
 
# Your helper functions for the Intune detection script go here
 
#endregion Your Helpers
 
# Script generated using Create-IntuneRemediation v$version
 
#---------------------------------------------- BEGIN ----------------------------------------------|
 
# --- Script Configuration ---
# * MODIFY THESE AS NEEDED *
```$ScriptVersion = "1.0.0" # Version of the script
```$logPath = "```$env:SystemDrive\Logs\IR" # Ensure this path is pulled with Intune collects diagnostics
```$logName = "template" # Name of the log file
```$StateKey = "HKLM:\SOFTWARE\IntuneRemediations" # Base registry key for storing remediation state
```$InteractiveLogging = ```$false # Enable/disable display console output for troubleshooting
```$verboseLogging = ```$false # Enable/disable script transcript for troubleshooting
```$retainDays = 30 # How long to retain logs after a successful remediation has run (default is 30 days)
# -------------------------------------
 
#region Do Not Modify
# * DO NOT MODIFY THESE *
```$ErrorActionPreference = 'Stop' # Ensures try/catch works reliably [do not modify]
```$logFile = Join-Path ```$logPath "```$logName.log" # Full path to the log file [do not modify]
```$baseKey = Join-Path ```$StateKey "```$logName" # Base registry key for storing remediation state [do not modify]
```$exitCode = 0 # Intitalize exit code for compliance [do not modify]
```$StartTime = Get-Date # Record script execution start time [do not modify]
```$detectionOutput = [System.Collections.Generic.List[string]]::new() # Detection output to display in Intune admin center [do not modify]
Restart-64Bit # Ensure we are running as a 64-bit process on an x64 system [do not modify]
if (```$verboseLogging) { Start-Transcript -Path (Join-Path ```$logPath "```${logName}_verbose.log") -Append}
Write-Log "***** Starting detection script actions. *****" -Component 'Detection Begin'
Write-Log "Detection script version: ```$ScriptVersion" -Component 'Version'
# -------------------------------------
#endregion Do Not Modify
 
try {
    <# INSTRUCTIONS:
        - Only modify the detection content within this Try block!
        - You only need to check for failures. If checks fail, set ```$exitCode = 1. If script fails, set ```$exitCode = 2.
        - Use Write-Log to output messages to the log (also ouputs to the console if ```$InteractiveLogging is ```$true).
        If you want to include the output in the Intune admin center's detection output, add -StdOut
        to the end of the line. Max length of all aggregated messages is 2,048 characters total.
 
    # ----------------------------------------------------
    # --------- Your detection logic starts here ---------
    # --------------------------------------------------#>
 
    # example:
    ```$RegPath = "HKLM:\SOFTWARE\Contoso\Settings"
    ```$ValueName = "Enabled"
 
    ```$Path = Get-Item -Path ```$RegPath -ErrorAction SilentlyContinue
    if (-not ```$Path) {
        Write-Log "[FAIL] Registry key ```$(```$RegPath) is missing." -Type 3 -StdOut
        ```$exitCode = 1
    } else {
        ```$Value = Get-ItemPropertyValue -Path ```$RegPath -Name ```$ValueName -ErrorAction SilentlyContinue
        if (```$null -ne ```$Value) {
            # This Write-Log line adds the registry value to the log, but doesn't add it to the Intune detection output (no -StdOut parameter).
            Write-Log "Retrieved ```$(```$ValueName) registry value: ```$(```$Value)"
        }
 
        if (```$Value -ne 1) {
            # This Write-Log line is written to the log, displays as an error in the console, and is included in the Intune admin center's detection output.
            Write-Log "[FAIL] ```$(```$ValueName) registry value is incorrect or missing." -Type 3 -StdOut
            # Set compliance state to false since the registry value is not as expected and trigger remediation from the Compliance Result section below.
            ```$exitCode = 1
        }
    }
 
    # ----------------------------------------------------
    # ---------- End of your detection logic ----------
    # ----------------------------------------------------
 
  # Compliance Result * DO NOT MODIFY *
    if (```$exitCode -eq 0) { # Log will not be saved if there are no issues to log and the device hasn't been non-compliant recently.
        Write-Log "[PASS] The device is compliant." -StdOut # Intune detection output
      # Delete existing remediation logs for issues that have been remediated longer than ```$retainDays ago.
        Remove-ExpiredRemediationLog -RetainDays ```$retainDays
        exit ```$exitCode
    } elseif (```$exitCode -eq 2){
        Write-Log "[FAIL] The device is non-compliant due to a script failure." -Type 3 -StdOut
        exit ```$exitCode
    } else { # Device is not compliant
        Write-Log "[FAIL] The device is non-compliant."
        exit ```$exitCode
    }
} catch { # * DO NOT MODIFY *
    ```$exitCode = 2
    Write-Log "[ERROR] ```$(```$_.Exception.Message)" -Type 3 -StdOut
    exit ```$exitCode
} finally { # * DO NOT MODIFY *
    ```$EndTime = Get-Date
  # Create registry checkpoint for detection results
    Detection-Result -logName ```$logName -ScriptVersion ```$ScriptVersion -StartTime ```$StartTime -EndTime ```$EndTime -exitCode ```$exitCode
  # Delete existing remediation logs for issues that have been remediated longer than ```$retainDays ago.
    Remove-ExpiredRemediationLog -RetainDays ```$retainDays
    Write-Output (```$detectionOutput -join " | ") # Intune detection output
    if (```$verboseLogging) { Stop-Transcript }
}
 
`"@
`$makeExampleDet | Out-File -FilePath "$path\scripts\Example-Detection.ps1" -Encoding UTF8 -Force
"@

$exampleDet | Out-File -FilePath "$taskFolderPath\Example-Detection.ps1" -Encoding UTF8 -Force

#endregion Example Detection Script Creation

#region Example Remediation Script Creation
#--------------------------------------------- Create Example File --------------------------------------------
$exampleRem = @"
`$makeExampleRem = @"
<#
.SYNOPSIS
    Standard Intune Remediation Script Template with CMTrace-compatible logging
    and registry checkpoint reporting.
 
.DESCRIPTION
    This script template provides a starting point for creating Intune Remediation scripts.
 
.FEATURES
    - CMTrace-compatible logging
    - Automatic relaunch in 64-bit PowerShell when required
    - Remediation run counter
    - Remediation result tracking in the registry
    - Transcript logging support for troubleshooting
 
.NOTES
    Registry Location:
        HKLM:\SOFTWARE\IntuneRemediations\<LogName>
 
    Log File:
        <SystemDrive>\Logs\IR\<LogName>.log
 
    Recorded Metadata:
        - RunId (correlation identifier)
        - RunCount
        - Remediation result
        - Runtime duration
        - Remediation timestamps
        - Last detection failure details
        - Script version
 
    Version: 1.0.0
    Author: Jeff Gilbert
 
.EXIT CODES
    0 = Remediation successful
    1 = Remediation failed
    2 = Error encountered
 
#>
 
#region Logging
# Template logging function for the Intune remediation script
# * DO NOT MODIFY *
function Write-Log {
 
    #-- THIS FUNCTION REQUIRES THE FOLLOWING BE DECLARED IN THE MAIN SCRIPT --#
    # ```$logPath
    # ```$logName
    # ```$detectionOutput = [System.Collections.Generic.List[string]]::new()
    #-------------------------------------------------------------------------#
 
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = ```$true, Position = 0)] [ValidateNotNullOrEmpty()] [string] ```$Message,
        [Parameter(Mandatory = ```$false, Position = 1)] [string] ```$Component = "Remediation",
        [Parameter(Mandatory = ```$false, Position = 2)] [ValidateSet(1, 2, 3)] [int] ```$Type = 1,
        [Parameter(Mandatory = ```$false)] [switch] ```$StdOut
    )
  # Capture once to avoid drift between Time/Date
    ```$now = Get-Date
    ```$Time = ```$now.ToString('HH:mm:ss.ffffff', [System.Globalization.CultureInfo]::InvariantCulture)
    ```$Date = ```$now.ToString('MM-dd-yyyy', [System.Globalization.CultureInfo]::InvariantCulture)
  # Escape message for CMTrace payload (XML-like). SecurityElement.Escape handles &, <, >, " (quotes) safely
    ```$escapedMessage = [System.Security.SecurityElement]::Escape(```$Message).Replace('&apos;', "'")
  # Identify if it was a function that called Log and make it the component"
    ```$scriptFullPath = ```$PSCommandPath ; ```$scriptName = Split-Path -Path ```$scriptFullPath -Leaf
    ```$caller = (Get-PSCallStack)[1].Command
    if (-not (```$caller -eq ```$scriptName)){ ```$Component = ```$caller }
  # Build CMTrace entry (keep empty attributes as per CMTrace spec)
    ```$logLine = ('<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="" type="{4}" thread="" file="">' ````
                -f ```$escapedMessage, ```$Time, ```$Date, ```$Component, ```$Type)
    try {
      # Write the log entry
        ```$dir = Split-Path -Path ```$logFile -Parent
      # Ensure log directory exists
        New-Item -ItemType Directory -Path ```$dir -Force | Out-Null
      # Write message to the log file
        ```$logLine | Out-File -LiteralPath ```$logFile -Append -Encoding UTF8
      # Add message to detection results sent to the Intune admin center
        if (```$StdOut) { ```$detectionOutput.Add(```$Message) }
      # Write all log lines to console for interactive testing/transcript capture
        if (```$InteractiveLogging) {
            switch (```$Type) {
                1 { Write-Host ```$Message -ForegroundColor Gray }
                2 { Write-Host ```$Message -ForegroundColor Yellow }
                3 { Write-Host ```$Message -ForegroundColor Red }
                default { Write-Host ```$Message }
            }
        }
    }
    catch { # If writing fails write error
        Write-Error -Message ("Failed to write log to '{0}': {1}" -f ```$logFile, ```$_.Exception.Message)
        return
    }
}
#endregion Logging
 
#region Helpers
function Restart-64Bit {
    # If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process first
    if ("```$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") {
        if (Test-Path "```$(```$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") {
            & "```$(`$env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "```$PSCommandPath"
            Exit ```$lastexitcode
            }
        }
}
 
function Increment-RemediationRunCount {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]```$LogName
    )
 
    try {
 
        if (-not (Test-Path -LiteralPath ```$baseKey)) { New-Item -Path ```$baseKey -Force | Out-Null }
         
        ```$existing = Get-ItemProperty -Path ```$baseKey -Name "RunCount" -ErrorAction SilentlyContinue
        ```$currentCount = if (```$null -ne ```$existing) { [int]```$existing.RunCount } else { 0 }
 
        ```$newCount = ```$currentCount + 1
 
        New-ItemProperty ````
            -Path ```$baseKey ````
            -Name "RunCount" ````
            -Value ```$newCount ````
            -PropertyType DWord ````
            -Force | Out-Null
 
        return ```$newCount
    }
    catch {
        Write-Log "Failed to increment remediation run count: ```$(```$_.Exception.Message)" -Type 2
        return 0
    }
}
 
function Remediation-Result {
    param(
        [string]```$LogName,
        [string]```$ScriptVersion,
        [string]```$RunId,
        [string]```$LastRemediationResult,
        [datetime]```$StartTime,
        [datetime]```$EndTime = (Get-Date),
        [string]```$Details = "".
        [string]```$exitCode
    )
 
    try {
        if (-not (Test-Path ```$baseKey)) { New-Item -Path ```$baseKey -Force | Out-Null }
        ```$DurationSeconds = [math]::Round(((```$EndTime).ToUniversalTime() -
        (```$StartTime).ToUniversalTime()).TotalSeconds, 2)
 
        ```$LastDetectionFailure = (Get-ItemProperty -Path ```$baseKey -Name "DetectionState" -ErrorAction SilentlyContinue).DetectionState
 
        if (```$RemediationSucceeded -ne ```$true) {
            ```$RemediationResult = "Fail"
            ```$RemediationDetails = (```$detectionOutput -join " | ")
        } else {
            ```$RemediationResult = "Pass"
            ```$RemediationDetails = "[PASS] All checks successful"
        }
 
        ```$properties = [ordered]@{
            LastDetectionFailure = ```$LastDetectionFailure
            RemediationDetails = ```$RemediationDetails
            RemediationResult = ```$RemediationResult
            RemediationBeginTimeUtc = ```$StartTime.ToUniversalTime().ToString("o")
            RemediationEndTimeUtc = ```$EndTime.ToUniversalTime().ToString("o")
            RemediationRuntimeSeconds = ```$DurationSeconds.ToString()
            RunId = ```$RunId
            RemediationScriptVersion = ```$ScriptVersion
            RemediationScriptExitCode = ```$exitCode
        }
 
        foreach (```$property in ```$properties.GetEnumerator()) {
            New-ItemProperty ````
                -Path ```$baseKey ````
                -Name ```$property.Key ````
                -Value ```$property.Value ````
                -PropertyType String ````
                -Force | Out-Null
        }
 
        New-ItemProperty ````
            -Path ```$baseKey ````
            -Name "RunCount" ````
            -Value ([int]```$script:RunCount) ````
            -PropertyType DWord ````
            -Force | Out-Null
 
        Write-Log "Registry checkpoint updated: ```$baseKey"
        Write-Log "***** End of remediation script actions. *****"
    }
    catch {
        ```$exitCode = 2
        Write-Log "Failed to write remediation registry result: ```$(```$_.Exception.Message)" -Type 2
        Write-Log "***** End of remediation script actions. *****"
    }
}
 
#endregion Helpers
 
#region Your Helpers
 
# Your helper functions for the Intune remediation script go here
 
#endregion Your Helpers
 
# Script generated using Create-IntuneRemediation v$version
 
#---------------------------------------------- BEGIN ----------------------------------------------|
# --- Script Configuration ---
# * MODIFY THESE AS NEEDED *
```$scriptVersion = "1.0.0" # Script version
```$logPath = "```$env:SystemDrive\Logs\IR" # Log path must match the path in the detection script
```$logName = "template" # Log name must match the name in the detection script
```$stateKey = "HKLM:\SOFTWARE\IntuneRemediations" # Regkey must match the regkey in the detection script
```$interactiveLogging = ```$false # Display console output for troubleshooting
```$verboseLogging = ```$false # Enables script transcript for troubleshooting
# -------------------------------------
 
#region Do Not Modify
# * DO NOT MODIFY THESE *
```$ErrorActionPreference = 'Stop' # Ensures try/catch works reliably [do not modify]
```$logFile = Join-Path ```$logPath "```$logName.log" # Full path to the log file [do not modify]
```$baseKey = Join-Path ```$stateKey "```$logName" # Base registry key for storing remediation state [do not modify]
```$exitCode = 0 # Intitalize exit code for compliance [do not modify]
```$RunId = [guid]::NewGuid().ToString() # Generates a new Correlation Id (CID) on every run [do not modify]
```$detectionOutput = [System.Collections.Generic.List[string]]::new() # Detection output to display in Intune admin center [do not modify]
```$StartTime = Get-Date # Record script execution start time [do not modify]
Restart-64Bit # Ensure we are running as a 64-bit process on an x64 system [do not modify]
```$RunCount = Increment-RemediationRunCount -LogName ```$logName # Keep track of how many times this script has run [do not modify]
if (```$verboseLogging) { Start-Transcript -Path (Join-Path ```$logPath "```${logName}_verbose.log") -Append }
Write-Log "***** Starting remediation script actions. *****"
Write-Log "Remediation script version: ```$scriptVersion." -Component "Version"
Write-Log "RunId: ```$RunId"
Write-Log "Remediation Run Count: ```$RunCount"
# -------------------------------------
#endregion Do Not Modify
 
try {
 
    <# INSTRUCTIONS:
    For any remediation failures:
     - Set ```$exitCode = 1 for remediation failures.
     - Set ```$exitCode = 2 for script failures.
     - Add -StdOut at the end of the Write-Log commands to append issues in the registry checkpoint RemediationDetails value
     
    # ----------------------------------------------------
    # --------- Your remediation logic goes here ---------
    # --------------------------------------------------#>
 
    # example:
    ```$RegPath = "HKLM:\SOFTWARE\Contoso\Settings"
    ```$ValueName = "Enabled"
 
    if (-not (Test-Path ```$RegPath)) {
      # Create the registry key if it does not exist
        Write-Log "Contoso settings registry key not found." -Type 2
        New-Item -Path ```$RegPath -Force | Out-Null
 
      # Validate the regkey was successfully created
        If (-not (Test-Path ```$RegPath)) {
            Write-Log "Failed to create Contoso settings registry key." -Type 3 -StdOut
            ```$RemediationSucceeded = ```$false
            ```$exitCode = 1
        } else {
            Write-Log "Contoso settings registry key successfully created."
        }
    }
     
    if ((Get-ItemProperty -Path ```$RegPath).```$ValueName -ne 1) {
      # Create the registry value if it does not exist
        New-ItemProperty -Path ```$RegPath -Name ```$ValueName -Value 1 -PropertyType DWord -Force | Out-Null
 
      # Validate the registry value was successfully created
        If ((Get-ItemProperty -Path ```$RegPath).```$ValueName -ne 1) {
            Write-Log "Failed to set ```$ValueName to 1 in ```$RegPath." -Type 3 -StdOut
            ```$RemediationSucceeded = ```$false
            ```$exitCode = 1
        } else {
            Write-Log "```$(```$ValueName) successfully set to 1 in ```$RegPath."
            ```$RemediationSucceeded = ```$true
        }
    } else {
        Write-Log "```$ValueName is already set to 1 in ```$RegPath."
    }
 
    # ----------------------------------------------------
    # --------- End of your remediation logic ---------
    # ----------------------------------------------------
 
} catch { # * DO NOT MODIFY *
    ```$exitCode = 2
    Write-Log "[ERROR] ```$(```$_.Exception.Message)" -Type 3 -StdOut
    Write-Log "***** End of remediation script actions. *****"
} finally { # * DO NOT MODIFY *
   ```$EndTime = Get-Date
  # Create registry checkpoint for remediation results
    Remediation-Result -logName ```$logName -ScriptVersion ```$ScriptVersion -StartTime ```$StartTime -EndTime ```$EndTime -RunId ```$RunId -exitCode ```$exitCode
    if (```$verboseLogging) { Stop-Transcript }
}
`"@
`$makeExampleRem | Out-File -FilePath "$path\scripts\Example-Remediation.ps1" -Encoding UTF8 -Force
"@

$exampleRem | Out-File -FilePath "$taskFolderPath\Example-Remediation.ps1" -Encoding UTF8 -Force

#endregion Example Remediation Script Creation

#region Create Example Files

$genExamples = @"
Write-Output "Creating example detection and remediation scripts..."
 
`$ExampleDetectionPath = Join-Path `$PSScriptRoot 'Example-Detection.ps1'
& `$ExampleDetectionPath
 
`$ExampleRemediationPath = Join-Path `$PSScriptRoot 'Example-Remediation.ps1'
& `$ExampleRemediationPath
 
Write-Output "Example detection and remediation scripts created successfully in the scripts directory."
"@

$genExamples | Out-File -FilePath "$taskFolderPath\Generate-Examples.ps1" -Encoding UTF8 -Force
Write-Output " TASK: Generate example files created"
#endregion Create Example Files

#region Troubleshooting Help
# -------------------------------------------- Create the detection.ps1 file -------------------------------------------
$troubleshootingHelp = @"
# Intune Remediation Troubleshooting
 
For Intune Remediations, use the workflow below when troubleshooting a device. It follows the remediation lifecycle from assignment through script execution, reporting, and the next scheduled evaluation.
 
## High-Level Workflow
 
``````text
Intune Service
   |
   v
Policy Assignment
   |
   v
IME Policy Sync
(IntuneManagementExtension.log)
   |
   v
Scripts Downloaded
(C:\Windows\IMECache\HealthScripts)
   |
   v
Detection Script Runs
(AgentExecutor.log / HealthScripts.log)
   |
   +-- Exit 0 = Compliant --> Report Success
   |
   +-- Exit 1 = Issue Found
           |
           v
      Remediation Script Runs
      (AgentExecutor.log)
           |
           v
      Results Reported
      (HealthScripts.log)
           |
           v
      Next Scheduled Evaluation
``````
 
A remediation script runs only when the detection script exits with code 1. If detection returns exit code 0, remediation is skipped.
 
## Log Investigation Path
 
### 1. Get the Remediation GUID
You can identify the GUID of an Intune Remediation from the Intune admin center browser address bar. The GUID is useful when troubleshooting policy deployment, reviewing logs, or locating the downloaded scripts.
 
**From the Intune Admin Center**
 
1. Navigate to Devices → Scripts and remediations → Remediations.
2. Select the remediation script.
3. In the browser address bar, locate the value after /id/. This value is the remediation GUID.
 
**Example**
``````text
https://intune.microsoft.com/#view/Microsoft_Intune_Enrollment/UXAnalyticsScriptMenu/~/overview/id/23105e5c-553d-4b05-ac3e-09c677ef0ab4/scriptName/Autopatch%20Health%20Check/isFirstParty~/false
``````
 
**GUID**
``````text
23105e5c-553d-4b05-ac3e-09c677ef0ab4
``````
 
### 2. IntuneManagementExtension.log
 
**Location**
 
``````text
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log
``````
 
Use this log to determine:
 
- Whether the device received the policy
- Whether IME successfully synced
- Whether the remediation assignment was evaluated
- Whether download or processing failures occurred
 
**Useful search terms:**
 
- `HealthScripts`
- `Remediation`
- `Policy`
- `Assignment`
 
Start here when you need to determine whether the device knows about the remediation assignment.
 
### 3. Verify Script Download
 
Check the remediation cache:
 
``````text
C:\Windows\IMECache\HealthScripts
``````
 
A package is typically stored in a GUID-named folder containing the detection and remediation scripts:
 
``````text
C:\Windows\IMECache\HealthScripts\<GUID>\
    DetectionScript.ps1
    RemediationScript.ps1
``````
 
- Confirm the scripts downloaded
- Confirm the content is the expected version
- Confirm the detection and remediation files are present
 
If the expected package is absent, focus on assignment, IME synchronization, or download processing.
 
### 4. HealthScripts.log
 
``````text
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\HealthScripts.log
``````
 
This is the primary remediation-specific lifecycle log. Review it for policy processing, detection execution, detection output and exit code, remediation launch, remediation results, and status reporting.
 
``````text
Detection Script Exit Code: 1
Launching remediation script
 
Detection Script Exit Code: 0
Remediation skipped
``````
 
### 5. AgentExecutor.log
 
``````text
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log
``````
 
Use this log for the PowerShell execution layer. Look for script output, exceptions, exit codes, access-denied errors, missing paths, registry errors, and execution-context problems.
 
## Registry Validation
 
IME script policy data can be inspected under:
 
``````text
HKLM\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts
``````
 
Use this area to confirm that policy data exists and to correlate package identifiers and updates. The remediation framework examples also record checkpoints under:
 
``````text
HKLM\SOFTWARE\<COMPANY>\IntuneRemediations\<ScriptName>
``````
 
Checkpoint values can help confirm the last detection and remediation results, run times, correlation Id's, script version, and more.
 
## Troubleshooting Decision Tree
 
### Remediation never runs
 
- Confirm the device or user assignment in Intune
- Review `IntuneManagementExtension.log`
- Review `HealthScripts.log`
- Verify that the Microsoft Intune Management Extension service is installed and running
 
### Detection runs but remediation never runs
 
Review `HealthScripts.log` for the detection exit code. Exit 0 means compliant and skips remediation. Exit 1 means an issue was detected and remediation should run.
 
### Remediation runs but doesn't fix the issue
 
- Review `AgentExecutor.log`
- Review the script-specific error log, such as `C:\Logs\IR\<logname>_ERR.log`
- Check exceptions, access problems, validation failures, and user-versus-system execution context
 
## Fastest Troubleshooting Path
 
1. ``HealthScripts.log``
2. ``AgentExecutor.log``
3. ``C:\Windows\IMECache\HealthScripts``
4. ``IntuneManagementExtension.log``
5. ``Registry checkpoint key``
 
This sequence usually answers the key questions: Was the remediation assigned? Did detection run? What exit code was returned? Did remediation launch? What error occurred?
 
## Collect-RemediationLogs
This script (available in the PowerShell Gallery) collects Microsoft Intune Management Extension and Remediation troubleshooting information from a Windows endpoint.
     
The script collects:
- Remediation-related Intune Management Extension logs
- Custom Intune Remediation logs [`$customLogPath]
- Custom registry checkpoint paths [`$checkpointPath]
- IME service information
- HealthScripts cache inventory
- Script package file hashes
- Relevant registry information
- DeviceManagement Enterprise Diagnostics event log
- Device and enrollment summary
- Recent IME log entries containing remediation-related terms
 
Run this from an elevated PowerShell session. It creates a timestamped ZIP file in C:\Windows\Temp by default.
 
**TIP:**
 
    Run this script as System using PSExec64.exe and add the -IncludeCachedScripts command line switch to collect the cached detection and remediation script files along with the logs.
 
Install from the PowerShell Gallery to run on your device:
``````powershell
Install-Script Collect-RemediationLogs
``````
 
Or just save it locally so you can run it on remote devices (recommended):
``````powershell
Save-Script Collect-RemediationLogs -Path <path>
``````
 
## References
 
- [Microsoft Learn: Use Remediations to detect and fix support issues](https://learn.microsoft.com/en-us/intune/device-management/tools/deploy-remediations)
- [Microsoft Learn: Understand Microsoft Intune Management Extension](https://learn.microsoft.com/en-us/intune/device-management/tools/management-extension-windows)
 
"@

$troubleshootingHelp | Out-File -FilePath "$taskFolderPath\troubleshooting.md" -Encoding UTF8 -Force

#endregion Troubleshooting Help

#region TaskDlScript
$taskTsScript = @"
`$tsFolderPath = ".\troubleshooting"
# Ensure the troubleshooting folder exists
    if (-not (Test-Path -Path `$tsFolderPath)) {
        New-Item -ItemType Directory -Path `$tsFolderPath | Out-Null
    }
 
Save-Script Collect-RemediationLogs -Path './troubleshooting' -Force
Write-Output "Collect-RemediationLogs script has been saved to the troubleshooting folder."
"@


$taskTsScript | Out-File -FilePath "$taskFolderPath\TaskTsScript.ps1" -Encoding UTF8 -Force

#endregion TaskTsScript
    
#region TaskHelp
$taskHelp = @"
`$tsFolderPath = ".\troubleshooting"
# Ensure the troubleshooting folder exists
    if (-not (Test-Path -Path `$tsFolderPath)) {
        New-Item -ItemType Directory -Path `$tsFolderPath | Out-Null
    }
 
Copy-Item -LiteralPath './.vscode/troubleshooting.md' -Destination "`$tsFolderPath/troubleshooting.md" -ErrorAction Stop
Write-Output "Troubleshooting help has been copied to the troubleshooting folder."
"@


$taskHelp | Out-File -FilePath "$taskFolderPath\TaskHelp.ps1" -Encoding UTF8 -Force

#endregion TaskHelp

#region Finish up
# --------------------------------------------------- Finish up -------------------------------------------------------------
Write-Output "VS Code Workspace creation complete!"
Write-Output `n
$openWorkspace = Read-Host "Do you want to open the workspace in VS Code now? [Y or N]"
if ($openWorkspace -eq "Y" -or $openWorkspace -eq "y") {
    try {
        #invoke-item $workspaceFilePath -ErrorAction SilentlyContinue
        code $workspaceFilePath #-NoNewWindow -ErrorAction SilentlyContinue
        Write-Output "Workspace opened in VS Code."
        Exit
    }
    catch {
        Write-Host "Unable to open VS Code. Please check if VS Code is installed."
        # Uncomment the line below to see the error details
        # Write-Output "Error: $_"
    }
    
    $portable = Read-Host "Do you want to use VSCode Portable to open the workspace? [Y or N]"
        if ($portable -eq "Y" -or $portable -eq "y") {

            try {
                #If VS Code portable is already installed, use it.
                $vsCodePortablePath =  "$outDir\.VSCodePortable\Code.exe"
                if (Test-Path $vsCodePortablePath){
                    Start-Process -FilePath $vsCodePortablePath -ArgumentList """$workspaceFilePath"""
                    Write-Output "Workspace opened in VS Code Portable."
                    Exit
                }
            }
            catch {
            #VS Code portable is NOT installed
            $vsCodePortablePath =  "$outDir\.VSCodePortable\Code.exe"             
            $vscodePath = Join-Path $outDir ".VSCodePortable"
            if (-not (Test-Path -Path $vscodePath)) {
                New-Item -ItemType Directory -Path $vscodePath | Out-Null
            }
            
            # Find processor architecture for download link https://code.visualstudio.com/download
            $architecture = (Get-WMIObject -Class Win32_Processor).Architecture
            if ($architecture -eq 9) { #x64
                $vscodeZip = "https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-archive"
            } elseif ($architecture -eq 5) { #arm64
                $vscodeZip = "https://code.visualstudio.com/sha/download?build=stable&os=win32-arm64-archive"
            } else {
                Write-Host "Unsupported architecture: $architecture" -ForegroundColor Red
            }    
                
            # Download VS Code portable .zip
            $vscodeFile = "VSCodePortable.zip"
            $vscodeOutFile = Join-Path $vscodePath $vscodeFile
            Write-Output " Downloading and extracting VS Code Portable..."
            Invoke-WebRequest -Uri $vscodeZip -OutFile $vscodeOutFile -UseBasicParsing

            if (-not (Test-Path -Path $vscodeOutFile)) {
                Write-Error "Failed to download VS Code Portable. Please check your internet connection."
                Write-Warning "Direct download link is $vscodeZip"
            }else {
            # Extract the downloaded .zip file
                Expand-Archive -Path $vscodeOutFile -DestinationPath $vscodePath -Force
            
            # Delete .zip file after extraction
                Remove-Item -Path $vscodeOutFile -Force
                Write-Output "VS Code Portable downloaded to $vscodePath"
            
            # Open workspace?
            $openWorkspace = Read-Host "Do you want to open the workspace in VS Code now? [Y or N]"
            if ($openWorkspace  -eq "Y" -or $openWorkspace  -eq "y") {
                $vscode = Join-Path -Path $vscodePath -ChildPath "\Code.exe" 
                start-process $vscode -ArgumentList """$workspaceFilePath""" -NoNewWindow -ErrorAction SilentlyContinue
                Write-Output "Workspace opened in VS Code."
                Write-Output "You can also open the workspace later by double-clicking the $Shortcut created in $outDir"
                Write-Output `n
                Start-Sleep -seconds 10
                Exit
            }
        }           
    }
}
} else {
    Write-Output "You chose not to open the workspace." `n
    Write-Output "You can open the workspace later by double-clicking the $workSpaceName workspace shortcut created in $outDir" `n
    Write-Output "Good-bye." `n
}
#endregion Finish up
Exit