Create-IntuneRemediation.ps1

<#PSScriptInfo
 
.VERSION 1.0.1
 
.GUID 3b2966d1-96be-4da2-99fe-485984dd14a4
 
.AUTHOR Jeff Gilbert
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS Intune
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
Version 1.0: Original published version.
#>


<#
.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.
    If you don't have Visual Studio Code installed, it will prompt you to download VS Code Portable.
 
.PARAMETER outDir
    The output directory where the workspace and files will be created. Default location is My Documents\IntuneRemediations.
 
.PARAMETER workSpaceName
    The name of the workspace to be created. Default is "MyRemediation".
 
.EXAMPLE
    .\IntuneRemediationSetup.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"
)
Clear-Host
$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 $workSpaceName workspace file..."

$workspaceFilePath = Join-Path $path "$workSpaceName.code-workspace"
Set-Content -Path $workspaceFilePath -Value $workspaceJson -Encoding UTF8 | Out-Null
Write-Host " $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
 
.DESCRIPTION
     
.PARAMETER
 
.EXAMPLE
 
.NOTES
 
#>
 
#-------------------------------------------------------- Functions ---------------------------------------------------------
 
 
#------------------------------------------------------- Begin Script -------------------------------------------------------
<#
No logging performed by default for detection scripts, as they do not make changes to the device.
Use write-host or write-output to display messages in the Intune portal's device status for your remediation.
#>
 
try {
    # Your script logic goes here.
    `$someCondition -eq "xyz" # This is just a placeholder for something you're checking for in your detection logic
 
    #----- Test if a specific condition is met -----
    if (`$someCondition -eq `$false) {
        Write-Output "No issues detected."
        Exit 0 # Detection: compliant
    }Else {
        Write-Output "Issues detected."
        Exit 1 # Detection: non-compliant
    }
}
catch {
    # Do this if a terminating exception happens
    Write-Error "An error occurred: $_"
}
 
# Stop-Transcript
Exit
 
"@

$detectionScript | Out-File -FilePath $path\scripts\$workSpaceName"_detection.ps1" -Encoding UTF8 -Force
Write-Output " Default detection script created"

# -------------------------------------------- Create the remediation.ps1 file -------------------------------------------
$remediationScript = @"
<#
.SYNOPSIS
 
.DESCRIPTION
     
.PARAMETER
 
.EXAMPLE
 
.NOTES
 
#>
 
#-------------------------------------------------------- Functions ---------------------------------------------------------
 
 
#------------------------------------------------------- Begin Script -------------------------------------------------------
# Logging optional for remediations because the script output is displayed in the Intune portal's device status for your remediation.
# Logging enabled in this example where Intune can grab it if needed.
`$logDir = "`$(`$env:ProgramData)\Microsoft\IntuneManagementExtension\Logs" # This path is pulled when Intune collects diagnostics.
`$logFile = "`$(`$logDir)\my_remediation.log" # Rename this log file.
Start-Transcript `$logFile
 
try {
    # Your script logic goes here
} catch {
    Write-Error "An error occurred: `$_"
}
 
Stop-Transcript
Exit
"@

$remediationScript | Out-File -FilePath $path\scripts\$workSpaceName"_remediation.ps1" -Encoding UTF8 -Force
Write-Output " Default 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 " Generate example file task created"


# --------------------------------------------------- Finish up -------------------------------------------------------------
Write-Output "Intune remediation scripts VS Code workspace creation completed"
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-Host "Failed to download VS Code Portable. Please check your internet connection." -ForegroundColor Red
                Write-Host "Direct download link is $vscodeZip" -ForegroundColor
            }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