Creating remediation actions for System Insights

This post was authored by Garrett Watumull, PM on the Windows Server team at Microsoft. Follow him @GarrettWatumull on Twitter.

Quick overview

System Insights enables you to configure custom remediation scripts to automatically address the issues detected by each capability. For each capability, you can set a custom PowerShell script for each prediction status. Once a capability returns a prediction status, System Insights automatically invokes the associated script to help address the issue reported by the capability, so that you can take correction action automatically rather than needing to manually intervene.

You can read more on how to set remediation actions in the System Insights management page. This blog, however, provides concrete examples and PowerShell scripts to help you get started writing your own remediation actions.

Parsing the capability output

The volume consumption forecasting capability and networking capacity forecasting capability report the most severe status across all of your volumes and network adapters respectively. Before writing any remediation scripts, we need to determine the specific volumes or network adapters that reported the result.

Fortunately, System Insights outputs the result of each capability into a JSON file, which contains the specific forecasting results for each volume or network adapter. This is really helpful, as this file format and the output schema allows you to easily programmatically determine the status of specific volumes and network adapters. For the default forecasting capabilities, the JSON file uses the following schema:

  1. Status: Top level status.
  2. Status Description: Top level status description.
  3. Prediction Results: An array of prediction results. For volume and networking forecasting, this contains an entry for each volume or network adapter. For total storage and CPU forecasting, this only contains one entry.
    1. Identifier: The GUID of the instance. (This field is present for all capabilities, but it is only applicable to volumes and network adapters.)
    2. Identifier Friendly Name: The friendly name of the instance.
    3. Status: The status for the instance.
    4. Status Description: The status description for the instance.
    5. Limit: The upper limit for that instance, e.g. the volume size.
    6. Observation Series: An array of historical data that’s inputted into the forecasting capability:
      1. DateTime: The date the data point was recorded.
      2. Value: The observed value, e.g. the used size of the volume.
    7. Prediction: An array of predictions based on the historical data in the Observation Series.
      1. DateTime: The date of the predicted data point.
      2. Value: The value predicted.

Using the schema above, you can now write a script to return all volumes that have a prediction status:

 

<#
Get-Volumes-With-Specified-Status
Retrieves all volumes that have a given prediction status. 

:param $Status: [string] Prediction status to look for.
:return: [array] List of volumes with the relevant status.
#>
Function Get-Volumes-With-Specified-Status {
    Param (
        $Status
    )

    $Volumes = @()
    # Get the JSON result and store it in $Output.
    $Output = Get-Content (Get-InsightsCapabilityResult -Name "Volume consumption forecasting").Output | ConvertFrom-Json
  
    # Loop through volumes, checking if they have the specified status.
    $Output.ForecastingResults | ForEach-Object {
        if ($_.Status -eq $Status) {
            # Format Id into expected format.
            $Id = $_.Identifier
            $Volumes += "\\?\Volume{$Id}\"
        }
    }
    $Volumes
}

Extending a volume

Now that you can determine the volumes that have a specific status, you can write more incisive remediation actions. One example is extending a volume when it’s forecasted to exceed the available capacity.
The script below is a best effort to extend a volume a specified percentage beyond its current size.

<#
Extend-Volume
If possible, extend the specified volume a specified percentage beyond its current size. If can't extend to this size, this function will extend to the maximum size possible. 

:param $VolumeId: [string] ID of the volume to extend.
:param $Percentage: [float] Percentage to extend the volume.
#>
Function Extend-Volume {
    Param (
        $VolumeId,
        $Percentage
    )

    $Volume = Get-Volume -UniqueId $VolumeId

    if ($Volume) {
        # See if the volume can be extended. 
        $Sizes = $Volume | Get-Partition | Get-PartitionSupportedSize

        # Must be able to extend by at least 1Mib
        if ($Sizes.sizeMax - $Volume.Size -le 1048576) {
            Write-Host "This volume can't be extended." -ForegroundColor Red
            return
        }

        $OldSize = $Volume.Size

        # Volume size if extended by specified percentage.
        $ExtendedSize = $Volume.Size * $Percentage

        # Select minimum of new size and max supported size.
        $NewSize = [math]::Min($ExtendedSize, $Sizes.sizeMax)
     
        try {
            # Extend partition
            $Volume | Get-Partition | Resize-Partition -Size $NewSize
            Write-Host "Successfully extended partition." -ForegroundColor Green
            Write-Host "   Old size: $OldSize."
            Write-Host "   New size: $NewSize."
       
         } catch {
             Write-Host "Failed to extend volume." -ForegroundColor Red
         }
    } 
    else {
        Write-Host "The volume with ID: $VolumeId wasn't found." -ForegroundColor Red
    }
}

Putting these together, you can extend all volumes that have reported a specific status:

Get-Volumes-With-Specified-Status $Status | ForEach-Object {
    Extend-Volume $_, $ResizePercentage
}

Running disk cleanup

For total storage consumption forecasting or volume consumption forecasting, rather than provision more capacity or extending a volume, you can free up space on your machine by deleting unused system files using Disk Cleanup. The script below allows you to configure Disk Cleanup preferences, places those preferences in the registry, and then runs Disk Cleanup across all drives on your machine using the settings in the registry. (Some of these fields only apply to the boot drive, but disk cleanup will automatically determine the appropriate fields to clean on each drive.)

To set your preferences, uncomment the categories listed at the beginning of the script. Once you have uncommented your preferences, specify an ID for this set of preferences and run the script:

$Id = 6
DiskCleanupScript.ps1 6

Warning: The following script is pretty long due to the many different options exposed by Disk Cleanup, but hopefully the actual logic to run disk clean up is pretty straightforward, which can be found at the bottom of the script.

 

param(
# Clean up ID must be an integer between 1-9999
[string] $UserCleanupId
)

<#
Create-Cleanup-List
Creates a list of the items Disk Cleanup will try to clean.

:return: [array] An array of the various items you wish to clean.
#>
Function Create-Cleanup-List {

    # Array to store the file types to clean up.
     $ToClean = @()

    <#
    Item: Temporary Setup Files
    Description: These files should no longer be needed. They were originally created by a setup program that is no longer running.
    #>
    # $ToClean += "Active Setup Temp Folders"

    <#
    Item: Old Chkdsk Files
    Description: When Chkdsk checks your disk drive for errors, it might save lost file fragments as files in your disk drive's root folder. These files are unnecessary and can be removed.
    #>
    $ToClean += "Old ChkDsk Files"

    <#
    Item: Setup Log Files
    Description: Files created by Windows.
    #>
    # $ToClean += "Setup Log Files"

    <#
    Item: Windows Update Cleanup
    Description: Windows keeps copies of all installed updates from Windows Update, even after installing newer versions of updates. Windows Update cleanup deletes or compresses older versions of updates that are no longer needed and taking up space. (You might need to restart your computer.)
    #>
    # $ToClean += "Update Cleanup"

    <#
    Item: Windows Defender Antivirus
    Description: Non-critical files used by Windows Defender Antivirus.
    #>
    # $ToClean += "Windows Defender"

    <#
    Item: Windows Upgrade Log Files
    Description: Windows upgrade log files contain information that can help identify and troubleshoot problems that occur during Windows installation, upgrade, or servicing. Deleting these files can make it difficult to troubleshoot installation issues.
    #>
    # $ToClean += "Windows Upgrade Log Files"

    <#
    Item: Downloaded Program Files
    Description: Downloaded Pgoram Files are ActiveX controls and Java applets downloaded automatically from the Internet when you view certain pages. They are temporarily stored in the Downloaded Program Files folder on your hard disk. 
    #>
    # $ToClean += "Downloaded Program Files"

    <#
    Item: Temporary Internet Files
    Description: The Temporary Internet Files folder contains webpages stored on your hard disk for quick viewing. Your personalized settings for webpages will be left intact.
    #>
    # $ToClean += "Internet Cache Files"

    <#
    Item: System Error Memory Dump Files
    Description: Remove system error memory dump files.
    #>
    # $ToClean += "System Error Memory Dump Files"

    <#
    Item: System Error Minidump Files
    Description: Remove system error minidump files.
    #>
    # $ToClean += "System Error Minidump Files"

    <#
    Item: Files discarded by Windows Update
    Description: Files from a previous Windows installation. As a precaution, Windows upgrade keeps a copy of any files that were not moved to the new version of Windows and were not identified as Windows system files. If you are sure that no user's personal files are missing after the upgrade, you can delete these files.
    #>
    # $ToClean += "Upgrade Discarded Files"

    <#
    Item: System created Windows Error Reporting Files
    Description: Files used for error reporting and solution checking.
    #>
    # $ToClean += "Windows Error Reporting Files"

    <#
    Item: Windows ESD Installation Files
    Description: You will need these files to Reset or Refresh your PC.
    #>
    # $ToClean += "Windows ESD Installation Files"

    <#
    Item: BranchCache
    Description: Files created by BranchCache service for caching data.
    #>
    # $ToClean += "BranchCache"

    <#
    Item: DirectX Shader Cache
    Description: Clean up files created by the graphics system which can speed up application load time and improve responsiveness. They will be re-generated as needed.
    #>
    # $ToClean = "D3D Shader Cache"

    <#
    Item: Previous Windows Installation(s)
    Description: Files from a previous Windows installation. Files and folders that may conflict with the installation of Windows have been moved to folders named Windows.old. You can access data from the previous Windows installations in this folder.
    #>
    # $ToClean += "Previous Installations"

    <#
    Item: Recycle Bin
    Description: The Recycle Bin contains files you have deleted from your computer. These files are not permanently removed until you empty the Recycle Bin.
    #>
    # $ToClean += "Recycle Bin"

    <#
    Item: RetailDemo Offline Content
    Description: 
    #>
    # $ToClean += "RetailDemo Offline Content"

    <#
    Item: Update package Backup Files
    Description: Windows saves old versions of files that have been updated by an Update package. If you delete the files, you won't be able to uninstall the Update package later.
    #>
    # $ToClean += "Service Pack Cleanup"

    <#
    Item: Temporary Files
    Description: Programs sometimes store temporary information in a TEMP folder. Before a program closes, it usually deletes this information. You can safely delete temporary files that have not been modified in over a week.
    #>
    # $ToClean += "Temporary Files"

    <#
    Item: Temporary Windows installation files
    Description: Installation files used by Windows setup. These files are left over from the installation process and can be safely deleted.
    #>
    # $ToClean += "Temporary Setup Files"

    <#
    Item: Thumbnail Cache
    Description: Windows keeps a copy of all your picture, video, and document thumbnails, so they can be displayed quickly when you open a folder. If you delete these thumbnails, they will be automatically recreated as needed.
    #>
    # $ToClean += "Thumbnail Cache"

    <#
    Item: User File History
    Description: Windows stores file versions temporarily on this disk before copying them to the designated File History disk. If you delete these files, you will lose some file history.
    #>
    # $ToClean += "User file versions"

    # Return cleaning list
    $ToClean
}

<#
Create-Cleanup-Id
Properly formats the cleanup Id. Cleanup Id must be 4 characters.

:return: [string] Properly formatted cleanup Id.
#>
Function Create-Cleanup-Id {
    # Determine how many zeros need to be inserted.
    $Zeros = 4 - $UserCleanupId.length

    if ($Zeros -lt 0) {
        Write-Host "The cleanup Id exceeds 4 characters. Specify an Id with four characters or less." -ForegroundColor Red
        return
    }

    $ZerosString = ""
    For ($i = 0; $i -lt $Zeros; $i++) {
        $ZerosString = "0$ZerosString"
    }
    "$ZerosString$UserCleanupId"
}

<#
Run-Disk-Cleanup
Runs disk cleanup using the cleanup Id and items specified in Create-Cleanup-Id and Create-Cleanup-List. 
#>
Function Run-Disk-Cleanup {
 
    $CleanupId = Create-Cleanup-Id
    if ($CleanupId) {
        # Must define cleanup preferences in the registry. 
        $RegKeyDirectory = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\"
        $RegKeyString = "StateFlags$CleanupId"
 
        Create-Cleanup-List | ForEach-Object {
            $RegistryPath = "$RegKeyDirectory$_"

            # Create regkey to specify which files to clean. 
            if (Test-Path $RegistryPath) {
                # Set the value to 2. Any other value won't trigger a cleanup.
                Set-ItemProperty -Path $RegistryPath -Name $RegKeyString -Value 2
            }
        }
        # Run disk cleanup using the preferences created in the registry.
        $Sagerun = "/SAGERUN:$CleanupId"
        cleanmgr.exe $Sagerun
     }
}

Run-Disk-Cleanup

Good luck!

Hopefully, these scripts show some of the possible remediation actions and help get you started writing your own. We hope you enjoy using this feature, and we’d love to hear your feedback and your experiences when creating your own custom scripts!