Create-IntuneRemediation.ps1
|
<#PSScriptInfo
.VERSION 1.1.0 .GUID fbee7913-0417-4b0b-8a76-0a4872b6eeba .AUTHOR Jeff Gilbert .COMPANYNAME .COPYRIGHT .TAGS Intune .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES v1.1.0 - 04.03.26 - Updated log cleanup logic to remove ERR logs older than 14 days. v1.0.0 - 02.12.26 - Original published version. 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. #> <# .SYNOPSIS Automates the creation of Intune remediation script pairs. .DESCRIPTION This script creates a Visual Studio Code workspace for Intune remediation scripts. It sets up the necessary folders, tasks, and scripts for creating and managing detection and remediation scripts. .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 For best results, run this script in a PowerShell terminal with administrative privileges. Ensure that the required tools and dependencies are installed and available in the system PATH. A scratch directory will be created in the specified output directory for storing test files. #> param ( [Parameter(Mandatory=$false)] [string]$workSpaceName = "MyRemediation", [Parameter(Mandatory=$false)] [string]$outDir = "IntuneRemediations" ) $version = "1.1.0" Clear-Host Write-Output "Running Intune Remediation Scripts Workspace Creator v$version ..." Write-Host `n $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 file" type = "shell" command = "./.vscode/makeExample.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' } # -------------------------------------------- Create the detection.ps1 file ------------------------------------------- $detectionScript = @" <# .SYNOPSIS Standard Intune Detection Script Template with CMTrace-compatible logging. .DESCRIPTION Provides a reusable framework for Intune Remediations detection scripts, including standardized logging, exit code handling, and detection output reporting. Detection results are logged to C:\Logs\IR\<LogName>.log. If one or more failures are detected: - The log is renamed to <LogName>_ERR.log - Detection details are returned to Intune - The remediation script is triggered If all checks pass: - The detection log is deleted - Intune reports the device as compliant .PARAMETER logName Name used for the detection and remediation log files. .PARAMETER version Version number of the detection script for change tracking and troubleshooting. .NOTES Author: <Author Name> Version: 1.0.0 Exit Codes: 0 = Compliant (Remediation not required) 1 = Non-compliant (Remediation required) Log Files: C:\Logs\IR\<LogName>.log C:\Logs\IR\<LogName>_ERR.log Interactive Logging: Set `$script:InteractiveLogging to `$true when testing locally. Set to `$false when deploying to production. .EXAMPLE Detect a missing folder: if (-not (Test-Path 'C:\SomePath')) { `$script:exitCode++ `$message = '[FAIL] Required path C:\SomePath does not exist' `$script:detectionOutput += `$message Log `$message } #> #------------------------------------------- DO NOT MODIFY OR DELETE THESE FUNCTIONS --------------------------------------- function Log { [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 ) # 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) # 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 directory exists if (`$dir -and -not (Test-Path -LiteralPath `$dir)) { New-Item -ItemType Directory -Path `$dir -Force | Out-Null } Add-Content -LiteralPath `$LogFile -Value `$logLine -Encoding UTF8 } catch { # If writing fails write error Write-Error -Message ("Failed to write log to '{0}': {1}" -f `$LogFile, `$_.Exception.Message) return } # Write all log lines to console for interactive testing/transcript capture if (`$script: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 } } } } function Start-Log { # When the detection script runs, a log file will be generated at C:\Logs\IR\<`$logName>.log. [CmdletBinding()] param ( [Parameter(Mandatory = `$true)] [ValidateNotNullOrEmpty()] [string]`$logName ) try { `$script:logPath = "`$env:SystemDrive\Logs\IR" # This path is pulled when Intune collects diagnostics `$script:logFile = Join-Path `$(`$script:logPath) "`$logName.log" `$script:errFile = Join-Path `$(`$script:logPath) `$logName"_ERR.log" Log "----------------------------- BEGIN `$(`$logName.ToUpper()) DETECTION -----------------------------" # Initialize the log file } catch { Write-Output "ERROR: `$(`$_.Exception.Message)" } } function Exit-Result { # If there are no issues, the detection log file will automatically be deleted. If issues are detected, the file # will be renamed <logName>_ERR.log and written to by the remediation script. If subsequent # remediation runs all pass, the ERR log file will be deleted after it has aged over 14 days. try { # Remediation exit codes for Intune reporting if (`$script:exitCode -ge 1) { Log "Non-zero exit code determined; remediation is necessary." # End the detection log with a footer to indicate the end of the detection section Log "------------------------------ END `$(`$logName.ToUpper()) DETECTION ------------------------------" # End detection section of the log file # Create ERR log file If (!(Test-Path `$script:errFile )){ Rename-Item `$script:logfile `$script:errFile -Force } else { Remove-Item `$script:errFile -Force -ErrorAction SilentlyContinue Rename-Item `$script:logfile `$script:errFile -Force } Write-Output (`$detectionOutput -join " | ") # This is what's displayed in the Intune admin center's detection output Exit 1 # Remediation needed } else { Write-Output "[PASS] All checks successful" # This is what's displayed in the Intune admin center's detection output # End the detection log with a footer to indicate the end of the detection section Log "------------------------------ END `$(`$logName.ToUpper()) DETECTION ------------------------------" # End detection section of the log file # Clean up old ERR logs if (Test-Path `$script:errFile){ `$retainDays = 14 `$cutOff = (Get-Date).AddDays(-`$retainDays) `$errAge = (Get-Item `$errFile).LastWriteTime if ( `$errAge -lt `$cutOff ){ Remove-Item `$script:errFile -Force -ErrorAction SilentlyContinue } } Remove-Item `$script:logFile -Force -ErrorAction SilentlyContinue # No error = no log Exit 0 # Remediation not needed } }catch{ Write-Output "ERROR: `$(`$_.Exception.Message)" } } #----------------------------------------------------------- Prep ---------------------------------------------------------- # 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 } } #------------------------------------------------------- Begin Script ------------------------------------------------------- # ONLY CHANGE THE NAME OF THE LOG FILE (`$logName) AND SCRIPT VERSION (`$version)IN THE FOLLOWING LINES # These are required for proper logging and exit code handling `$script:exitCode = 0 # Variable holding the number of failures detected `$script:detectionOutput = @() # Arrary holding remediation detection output to display in Intune admin center # ------------------------------------------------------------------- `$logName = "MyRemediation" # Change this to the name you want for your detection script. `$version = "1.0.0" # Change this to the version of your detection script. # ------------------------------------------------------------------- `$script:InteractiveLogging = `$true # Set to `$true to enable interactive logging for testing. Set to `$false for production use. Start-Log `$logName # Initialize the detection log file Log "Script version: `$version" -Type 1 #----------------------------------------------------- Helper Functions ---------------------------------------------------- # Add any additional functions you want to use in your detection script here #--------------------------------------------------------------------------------------------------------------------------- try { # Detection should only evaluate state. Do not modify system configuration in this script. # Add your detection logic below. If a failure is detected, increment the exit code and add a message to the detection output array. # Example: # if (-not (Test-Path "C:\SomePath")) { # `$script:exitCode++ # `$message = "[FAIL] Required path C:\SomePath does not exist" # `$script:detectionOutput += `$message # Log `$message # } # # if (-not (Test-Path "C:\SomePath1")) { # `$script:exitCode++ # `$message = "[FAIL] Required path C:\SomePath1 does not exist" # `$script:detectionOutput += `$message # Log `$message # } # If the detection script determines that remediation is necessary, the remediation script will be run } catch { # DO NOT MODIFY OR DELETE THE FOLLOWING LINES Log "ERROR: `$(`$_.Exception.Message)" -Type 3 } # DO NOT MODIFY OR DELETE THE FOLLOWING LINE. It is required for proper logging and exit code handling. Exit-Result "@ $detectionScript | Out-File -FilePath $path\scripts\$workSpaceName"_detection.ps1" -Encoding UTF8 -Force Write-Output " TEMPLATE: Detection script created" #$logFileName = $workspacename + "_remediation.log" $remediationScript = @" <# .SYNOPSIS Standard Intune Remediation Script Template with CMTrace-compatible logging. .DESCRIPTION Provides a reusable framework for Intune Remediations remediation scripts, including standardized logging, error handling, and 64-bit PowerShell relaunch support. The remediation script appends actions and results to: C:\Logs\IR\<LogName>_ERR.log This log is created by the associated detection script when a non-compliant condition is found. After remediation completes, Intune automatically reruns the detection script to verify that the issue has been resolved. .PARAMETER logName Name of the remediation log file. Must exactly match the LogName used by the associated detection script. .PARAMETER version Version number of the remediation script for change tracking and troubleshooting. .NOTES Author: <Author Name> Version: 1.0.0 Log File: C:\Logs\IR\<LogName>_ERR.log Exit Code: 0 = Remediation completed The logName value MUST BE IDENTICAL to the detection script logName. Interactive Logging: Set `$script:InteractiveLogging to `$true for local testing. Set to `$false for production deployments. .EXAMPLE Remediate a missing folder: if (-not (Test-Path 'C:\SomePath')) { New-Item -Path 'C:\SomePath' -ItemType Directory -Force Log 'Created folder: C:\SomePath' } #> #------------------------------------------- DO NOT MODIFY OR DELETE THESE FUNCTIONS --------------------------------------- function Log { [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 ) # 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) # 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 directory exists if (`$dir -and -not (Test-Path -LiteralPath `$dir)) { New-Item -ItemType Directory -Path `$dir -Force | Out-Null } Add-Content -LiteralPath `$logFile -Value `$logLine -Encoding UTF8 } catch { # If writing fails write error Write-Error -Message ("Failed to write log to '{0}': {1}" -f `$logFile, `$_.Exception.Message) return } # Write all log lines to console for interactive testing/transcript capture if (`$script: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 } } } } function Start-Log { # When the detection script runs, a log file will be generated at C:\Logs\IR\<`$logName>.log. [CmdletBinding()] param ( [Parameter(Mandatory = `$true)] [ValidateNotNullOrEmpty()] [string]`$logName ) try { `$script:logPath = "`$env:SystemDrive\Logs\IR" # This path is pulled when Intune collects diagnostics # `$script:logFile = Join-Path `$(`$script:logPath) "`$logName.log" `$script:logFile = Join-Path `$(`$script:logPath) `$logName"_ERR.log" # This is the remediation log file created if remediation is needed. # Start the remediation logging with a header to indicate the beginning of the remediation section Log "----------------------------- BEGIN `$(`$logName.ToUpper()) REMEDIATION -----------------------------" # Initialize the log file } catch { Write-Output "ERROR: `$(`$_.Exception.Message)" } } #----------------------------------------------------------- Prep ---------------------------------------------------------- # 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 } } #------------------------------------------------------- Begin Script ------------------------------------------------------- # `$logName SHOULD BE THE SAME NAME AS THE DETECTION LOG NAME (_ERR will be added to path when logging) # ------------------------------------------------------------------- `$logName = "MyRemediation" # Change this to EXACTLY MATCH the name of your detection script. `$version = "1.0.0" # Change this to the version of your remediation script. # ------------------------------------------------------------------- `$script:InteractiveLogging = `$true # Set to `$true to enable interactive logging for testing. Set to `$false for production use. Start-Log `$logName # Initialize the remediation log file Log "Script version: `$version" -Type 1 #----------------------------------------------------- Helper Functions ---------------------------------------------------- # Add any additional functions you want to use in your detection script here #--------------------------------------------------------------------------------------------------------------------------- try { `$ErrorActionPreference = 'Stop' # Ensures try { } catch { } works reliably # Remediation must be idempotent. Running this script multiple times should be safe. # Add your remediation logic below. Add messages to the log file as needed. # Example: # if (Test-Path -Path "C:\SomePath") { # Log "Folder exists: C:\SomePath" # } # else { # Log "Folder does not exist: C:\SomePath" # New-Item -Path "C:\SomePath" -ItemType Directory -Force | Out-Null # Log " Folder created: C:\SomePath" #} # # if (Test-Path -Path "C:\SomePath1") { # Log "Folder exists: C:\SomePath1" # } # else { # Log "Folder does not exist: C:\SomePath1" # New-Item -Path "C:\SomePath1" -ItemType Directory -Force | Out-Null # Log " Folder created: C:\SomePath1" #} } catch { Log "ERROR: `$(`$_.Exception.Message)" -Type 3 } finally { # DO NOT MODIFY OR DELETE THE FOLLOWING LINES # End the remediation log with a footer to indicate the end of the remediation section Log "----------------------------- END `$(`$logName.ToUpper()) REMEDIATION -----------------------------" } # If remediation is successful, the detection script will be re-run by Intune to verify that the issue has been resolved. Exit 0 "@ $remediationScript | Out-File -FilePath $path\scripts\$workSpaceName"_remediation.ps1" -Encoding UTF8 -Force Write-Output " TEMPLATE: Remediation script created" #--------------------------------------------- Create Example File -------------------------------------------- $makeApp = @" `$makeExample = @" # detection.ps1 # Detect if Notepad++ is installed ```$AppName = "Notepad++" ```$Installed = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { ```$_.DisplayName -like "*```$AppName*" } if (```$Installed) { Write-Output "```$AppName is installed." exit 0 # Detection: compliant } else { Write-Output "```$AppName is NOT installed." exit 1 # Detection: non-compliant } # remediation.ps1 # Install Notepad++ if not present ```$AppName = "Notepad++" ```$Installed = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { ```$_.DisplayName -like "*```$AppName*" } if (-not ```$Installed) { ```$InstallerUrl = "https://github.com/notepad-plus-plus/notepad-plus-plus/releases/latest/download/npp.8.6.7.Installer.x64.exe" ```$InstallerPath = "```$env:TEMP\npp_installer.exe" Invoke-WebRequest -Uri ```$InstallerUrl -OutFile ```$InstallerPath Start-Process -FilePath ```$InstallerPath -ArgumentList "/S" -Wait Remove-Item ```$InstallerPath -Force } `"@ `$makeExample | Out-File -FilePath "$path\scripts\examples.ps1" -Encoding UTF8 -Force "@ $makeApp | Out-File -FilePath "$taskFolderPath\makeExample.ps1" -Encoding UTF8 -Force Write-Output " TASK: Generate example file created" Write-Host `n # --------------------------------------------------- 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 } Exit |