481 lines
20 KiB
PowerShell
481 lines
20 KiB
PowerShell
<#----------------------------------------------------------------------------
|
||
LEGAL DISCLAIMER
|
||
This Sample Code is provided for the purpose of illustration only and is not
|
||
intended to be used in a production environment. THIS SAMPLE CODE AND ANY
|
||
RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||
EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||
MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a
|
||
nonexclusive, royalty-free right to use and modify the Sample Code and to
|
||
reproduce and distribute the object code form of the Sample Code, provided
|
||
that You agree: (i) to not use Our name, logo, or trademarks to market Your
|
||
software product in which the Sample Code is embedded; (ii) to include a valid
|
||
copyright notice on Your software product in which the Sample Code is embedded;
|
||
and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and
|
||
against any claims or lawsuits, including attorneys’ fees, that arise or result
|
||
from the use or distribution of the Sample Code.
|
||
|
||
This posting is provided "AS IS" with no warranties, and confers no rights. Use
|
||
of included script samples are subject to the terms specified
|
||
at http://www.microsoft.com/info/cpyright.htm.
|
||
|
||
|
||
Author: Tom Moser, PFE
|
||
Date: 5/13/2014
|
||
|
||
Version 1.0
|
||
-Initial Release
|
||
|
||
Usage: .\Cleanup-Disk.Ps1 [-NoReboot] [-LogPath <String>]
|
||
|
||
Switch: NoReboot - Specify this switch ONLY if you DO NOT want the server to reboot
|
||
post update. It is recommendend that you do NOT use this switch.
|
||
|
||
LogPath - Specify this parameter with a log location to write out the script log.
|
||
Will default to log.txt in the script directory.
|
||
|
||
Notes: In order to schedule the script successfully, the name must remain Cleanup-Disk.ps1.
|
||
The log file will contain all relevent information - no console output should be expected.
|
||
|
||
Summary:
|
||
This script requires KB2852386.
|
||
|
||
The script itself will perform the following:
|
||
-Verify the KB is installed
|
||
-Install the Desktop Experience feature
|
||
-Install a scheduled task that restarts the script 60 seconds after reboot
|
||
-Reboot, if necessary
|
||
-Update registry keys for cleanmgr.exe to run.
|
||
-Run cleanmgr.exe
|
||
-Reboot
|
||
-Remove Desktop Experience
|
||
-Reboot
|
||
-Remove scheduled task
|
||
-Exit
|
||
|
||
-----------------------------------------------------------------------------#>
|
||
|
||
Param([string]$LogPath="$(join-path $(split-path -parent $MyInvocation.MyCommand.Definition) log.txt)",
|
||
[switch]$NoReboot=$false)
|
||
|
||
|
||
if((get-hotfix KB2852386).InstalledOn -eq $null)
|
||
{
|
||
Write-Error "KB2862386 is required for script. Please install hotfix and re-run."
|
||
Exit
|
||
}
|
||
|
||
|
||
#Reg Paths/Vars
|
||
Set-Variable -Name ScriptRegKey -Value "HKLM:\Software\WinSXSCleanup" -Option Constant
|
||
Set-Variable -Name ScriptRegValueName -Value "Phase" -Option Constant
|
||
Set-Variable -Name ScriptSageValueName -Value "SageSet" -Option Constant
|
||
Set-Variable -Name ScriptSpaceBeforeValue -Value "SpaceBefore" -Option Constant
|
||
Set-Variable -Name SchTaskName -Value "CleanMgr Task Cleanup" -Option Constant
|
||
Set-Variable -Name ScriptDEStatusatStart -Value "DEInstalledAtStart" -Option Constant
|
||
Set-Variable -Name UpdateCleanupPath -value "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Update Cleanup" -Option Constant
|
||
Set-Variable -Name ServicePackCleanupPath -Value "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Service Pack Cleanup" -Option Constant
|
||
Set-Variable -Name VolumeCachesPath -Value "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches" -Option Constant
|
||
Set-Variable -Name StateFlagClean -Value 2 -Option Constant
|
||
Set-Variable -Name StateFlagNoAction -Value 0 -Option Constant
|
||
|
||
$ScriptPath = split-path -parent $MyInvocation.MyCommand.Definition
|
||
|
||
#Phase Constants
|
||
Set-Variable -Name PhaseInit -Value -1 -Option Constant
|
||
Set-Variable -Name PhaseStarted -Value 0 -Option Constant
|
||
Set-Variable -Name PhaseDEInstalled -Value 1 -Option Constant
|
||
Set-Variable -Name PhaseSageSetComplete -Value 2 -Option Constant
|
||
Set-Variable -Name PhaseSageRunStarted -Value 3 -Option Constant
|
||
Set-Variable -Name PhaseSageRunComplete -Value 4 -Option Constant
|
||
Set-Variable -Name PhaseDERemoved -Value 5 -Option Constant
|
||
Set-Variable -Name PhaseTaskRemoved -Value 6 -Option Constant
|
||
|
||
#import-module
|
||
Import-Module ServerManager
|
||
|
||
#read state value, use switch statement
|
||
|
||
Function DateStamp
|
||
{
|
||
return "$(Get-Date -UFormat %Y%m%d-%H%M%S):"
|
||
}
|
||
|
||
Function LogEntry([string]$LogData)
|
||
{
|
||
Add-Content $LogPath "$(DateStamp) $LogData"
|
||
}
|
||
|
||
Function GetCurrentState
|
||
{
|
||
return (Get-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -ErrorAction SilentlyContinue).Phase
|
||
}
|
||
|
||
Function CreateScheduledTask
|
||
{
|
||
Param([string]$ScriptPath,
|
||
[string]$TaskName,
|
||
[string]$fLogPath,
|
||
[string]$fNoReboot=$false)
|
||
try
|
||
{
|
||
$Scheduler = New-Object -ComObject "Schedule.Service"
|
||
$Scheduler.Connect("Localhost")
|
||
$root = $Scheduler.GetFolder("\")
|
||
$newTask = $Scheduler.NewTask(0)
|
||
$newTask.RegistrationInfo.Author = $TaskName
|
||
$newTask.RegistrationInfo.Description = ""
|
||
$newtask.Settings.StartWhenAvailable = $true
|
||
$trigger = $newTask.Triggers.Create(8) #Trigger at boot
|
||
$trigger.Delay = "PT60S"
|
||
$trigger.Id = "LogonTriggerId"
|
||
$newTask.Principal.UserId = "NT AUTHORITY\SYSTEM"
|
||
$newTask.Principal.RunLevel = 1
|
||
$newTask.Principal.LogonType = 5
|
||
|
||
$action = $newtask.Actions.Create(0)
|
||
$action.Path = "C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe"
|
||
|
||
if($fNoReboot -eq $true)
|
||
{
|
||
$action.Arguments = "-command $(join-path $ScriptPath cleanup-disk.ps1) -LogPath $fLogPath -NoReboot -NonInteractive -NoLogo -Version 2"
|
||
}
|
||
else
|
||
{
|
||
$action.Arguments = "-command $(join-path $ScriptPath cleanup-disk.ps1) -LogPath $fLogPath -NonInteractive -NoLogo -Version 2"
|
||
}
|
||
|
||
$root.RegisterTaskDefinition("CleanMgr Cleanup Task", $newTask, 6, "NT AUTHORITY\SYSTEM", $null , 4)
|
||
}
|
||
catch
|
||
{
|
||
LogEntry "Failed to register scheduled task."
|
||
LogEntry $Error[0].Exception
|
||
throw "Failed to register scheduled task..."
|
||
}
|
||
}
|
||
Function DeleteScheduledTask
|
||
{
|
||
Param([string]$TaskName)
|
||
c:\windows\system32\schtasks.exe /delete /TN "CleanMgr Cleanup Task" /f
|
||
}
|
||
|
||
if(Test-Path $ScriptRegKey)
|
||
{
|
||
$CurrentState = GetCurrentState
|
||
}
|
||
else
|
||
{
|
||
$CurrentState = $PhaseInit
|
||
}
|
||
|
||
|
||
|
||
LogEntry "CurrentState: $CurrentState"
|
||
LogEntry "NoReboot Flag: $NoReboot"
|
||
|
||
do
|
||
{
|
||
LogEntry "**** Current State: $CurrentState"
|
||
|
||
#Evalute current state against all possibilities.
|
||
Switch($CurrentState)
|
||
{
|
||
|
||
$PhaseInit
|
||
{
|
||
LogEntry "Switch: Null"
|
||
|
||
try
|
||
{
|
||
#Calculate and log freespace
|
||
$FreeSpace = (Get-WmiObject win32_logicaldisk | where { $_.DeviceID -eq $env:SystemDrive }).FreeSpace
|
||
if((Test-Path $ScriptRegKey) -eq $false)
|
||
{
|
||
New-Item -Path $ScriptRegKey
|
||
}
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptSpaceBeforeValue -Value $FreeSpace
|
||
LogEntry "PhaseInit: Current Free Space: $([Math]::Round(($FreeSpace / 1GB),2))GB"
|
||
|
||
#Check to see if DE is already installed.
|
||
#If yes, set reg key to 1, else 0. Used to prevent DE from uninstalling unintentionally.
|
||
if((Get-WindowsFeature Desktop-Experience).Installed -eq $true)
|
||
{
|
||
Set-ItemProperty -Path $ScriptRegKey -name $ScriptDEStatusAtStart -Value 1
|
||
}
|
||
else
|
||
{
|
||
Set-ItemProperty -Path $ScriptRegKey -name $ScriptDEStatusAtStart -Value 0
|
||
}
|
||
|
||
#Start Installing DE
|
||
LogEntry "Feature: Installing Desktop Experience."
|
||
$FeatureResult = Add-WindowsFeature Desktop-Experience
|
||
LogEntry "PhaseInit: Feature: ExitCode: $($FeatureResult.ExitCode)"
|
||
LogEntry "PhaseInit: Feature: RestartRequired: $($FeatureResult.RestartNeeded)"
|
||
LogEntry "PhaseInit: Feature: Success: $($FeatureResult.Success)"
|
||
|
||
#If DE fails, throw error.
|
||
if($FeatureResult.Success -eq $false -and $FeatureResult.RestartNeeded -eq "No")
|
||
{
|
||
throw "PhaseInit: Failed to install Desktop Experience. This is a required feature for WinSXS Cleanup."
|
||
}
|
||
|
||
#If DE exists with no change needed or success, update reg keys. Reboot if required. Create task.
|
||
elseif($FeatureResult.ExitCode -eq "NoChangeNeeded" -or ($FeatureResult.Success))
|
||
{
|
||
LogEntry "PhaseInit: Feature: Desktop Experience Installed. Updating $ScriptRegKey\$ScriptRegValueName to $PhaseStarted"
|
||
#New-Item $ScriptRegKey -Force
|
||
New-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseStarted -Force
|
||
|
||
if($NoReboot -eq $false -and $FeatureResult.RestartNeeded -eq "Yes")
|
||
{
|
||
LogEntry "PhastInit: Creating Scheduled Task..."
|
||
CreateScheduledTask -ScriptPath $ScriptPath -TaskName $SchTaskName -fLogPath $LogPath
|
||
LogEntry "PhaseInit: Created Scheduled Task $SchTaskName"
|
||
$CurrentState = GetCurrentState
|
||
Restart-Computer
|
||
Sleep 10
|
||
}
|
||
elseif($FeatureResult.ExitCode -eq "NoChangeNeeded")
|
||
{
|
||
LogEntry "DE Already Installed. No reboot required."
|
||
LogEntry "PhastInit: Creating Scheduled Task..."
|
||
CreateScheduledTask -ScriptPath "$ScriptPath" -TaskName $SchTaskName -fLogPath $LogPath -fNoReboot "`$$NoReboot"
|
||
LogEntry "PhaseInit: Created Scheduled Task $SchTaskName"
|
||
|
||
$CurrentState = GetCurrentState
|
||
}
|
||
else
|
||
{
|
||
CreateScheduledTask -ScriptPath "$ScriptPath" -TaskName $SchTaskName -fLogPath $LogPath -fNoReboot "`$$NoReboot"
|
||
LogEntry "Phaseinit: Restart switch not specified. Please manually reboot the server to continue cleanup."
|
||
Exit
|
||
}
|
||
}
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry $error[0]
|
||
exit
|
||
}
|
||
|
||
break
|
||
}
|
||
|
||
$PhaseStarted
|
||
{
|
||
LogEntry "PhaseStarted: Verifying DE installation..."
|
||
if((Get-WindowsFeature Desktop-Experience).Installed -eq $true) #check for pending reboot
|
||
{
|
||
LogEntry "PhaseStarted: DE Installed. Moving to PhaseDEInstalled."
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseDEInstalled
|
||
}
|
||
Else
|
||
{
|
||
LogEntry "PhaseStarted: DE not installed. Resetting phase to null."
|
||
New-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $null
|
||
}
|
||
|
||
$CurrentState = GetCurrentState
|
||
break
|
||
}
|
||
|
||
$PhaseDEInstalled
|
||
{
|
||
try
|
||
{
|
||
LogEntry "PhaseDEInstalled: Starting PhaseDEInstalled..."
|
||
LogEntry "PhaseDEInstalled: Setting SagetSet..."
|
||
#use static SageSet Value. Insert in to registry.
|
||
$SageSet = "0010"
|
||
Set-Variable -Name StateFlags -Value "StateFlags$SageSet" -Option Constant
|
||
LogEntry "PhaseDEInstalled: SageSet complete."
|
||
LogEntry "PhaseDEInstalled: Setting VolumeCaches reg keys..."
|
||
#Set all VolumeCache keys to StateFlags = 0 to prevent cleanup. After, set the proper keys to 2 to allow cleanup.
|
||
$SubKeys = Get-Childitem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches
|
||
Foreach ($Key in $SubKeys)
|
||
{
|
||
Set-ItemProperty -Path $Key.PSPath -Name $StateFlags -Value $StateFlagNoAction
|
||
}
|
||
|
||
LogEntry "PhaseDEInstalled: VolumeCaches keys set."
|
||
LogEntry "PhaseDEInstalled: Setting UPdate and Service Pack Keys..."
|
||
#Set all script reg values for persistence through reboots.
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptSageValueName -Value $SageSet
|
||
Set-ItemProperty -Path $UpdateCleanUpPath -Name $StateFlags -Value $StateFlagClean
|
||
Set-ItemProperty -Path $ServicePackCleanUpPath -Name $StateFlags -Value $StateFlagClean
|
||
LogEntry "PhaseDEInstalled: Done."
|
||
|
||
#Update state key
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseSageSetComplete
|
||
$CurrentState = GetCurrentState
|
||
LogEntry "PhaseDEInstalled: Complete."
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry "PhaseDEInstalled: Failed to update reg keys."
|
||
LogEntry $Error[0].Exception
|
||
}
|
||
break
|
||
}
|
||
|
||
$PhaseSageSetComplete
|
||
{
|
||
LogEntry "PhaseSageSetComplete: Starting cleanmgr."
|
||
try
|
||
{
|
||
$SageSet = (Get-ItemProperty -Path $ScriptRegKey -Name $ScriptSageValueName).SageSet
|
||
|
||
LogEntry "PhaseSageSetComplete: CleanMgr.exe running... "
|
||
$StartTime = Get-Date
|
||
&"C:\Windows\System32\Cleanmgr.exe" + " /sagerun:$SageSet"
|
||
Wait-Process cleanmgr
|
||
$EndTime = Get-Date
|
||
LogEntry "PhaseSageSetComplete: CleanMgr.exe complete..."
|
||
LogEntry "PhaseSageSetComplete: Seconds Elapsed: $((New-TimeSpan $StartTime $EndTime).TotalSeconds)"
|
||
LogEntry "PhaseSageSetComplete: Updating State..."
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseSageRunComplete
|
||
$CurrentState = GetCurrentState
|
||
LogEntry "PhaseSageSetComplete: Complete."
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry "PhaseSageSetComplete: ERROR."
|
||
LogEntry $Error[0].Exception
|
||
}
|
||
break
|
||
}
|
||
|
||
$PhaseSageRunComplete
|
||
{
|
||
try
|
||
{
|
||
$DEStatusInit = (Get-ItemProperty -Path $ScriptRegKey -Name $ScriptDEStatusAtStart)."$ScriptDEStatusAtStart"
|
||
LogEntry "PhaseSageRunComplete: Starting PhaseSageRunComplete."
|
||
LogEntry "PhaseSageRunComplete: Getting DE Status."
|
||
|
||
$DEStatus = (Get-WindowsFeature Desktop-Experience).Installed
|
||
|
||
LogEntry "PhaseSageRunComplete: DEInstalled = $DEStatus"
|
||
LogEntry "PhaseSageRunComplete: DEStatus at Start was $DEStatusInit"
|
||
|
||
if($DEStatusInit -eq 1)
|
||
{
|
||
LogEntry "PhaseSageRunComplete: DE removal not required. Continuing..."
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseDERemoved
|
||
$CurrentState = GetCurrentState
|
||
Continue
|
||
}
|
||
|
||
#remove DE
|
||
if($DEStatus -eq $true)
|
||
{
|
||
LogEntry "PhaseSageRunComplete: Removing DE"
|
||
$FeatureResult = Remove-WindowsFeature Desktop-Experience
|
||
|
||
if($NoReboot -eq $false -and $FeatureResult.Success -and $FeatureResult.RestartNeeded -eq "Yes")
|
||
{
|
||
LogEntry "PhaseSageRunComplete: Result: $($FeatureResult.Success)"
|
||
LogEntry "PhaseSageRunComplete: RestartNeeded: $($FeatureResult.RestartNeeded)"
|
||
|
||
LogEntry "PhaseSageRunComplete: Feature removed successfully."
|
||
LogEntry "PhaseSageRunComplete: Rebooting..."
|
||
Restart-Computer -Force
|
||
Sleep 10
|
||
}
|
||
elseif(($NoReboot -eq $false) -and ($FeatureResult.Success -eq $false) -and ($FeatureResult.RestartNeeded -eq "Yes"))
|
||
{
|
||
LogEntry "PhaseSageRunComplete: Result: $($FeatureResult.Success)"
|
||
LogEntry "PhaseSageRunComplete: RestartNeeded: $($FeatureResult.RestartNeeded)"
|
||
LogEntry "PhaseSageRunComplete: Reboot already pending. Rebooting..."
|
||
LogEntry "PhaseSageRunComplete: Rebooting..."
|
||
Restart-Computer -Force
|
||
Sleep 10
|
||
}
|
||
Else
|
||
{
|
||
LogEntry "PhaseSageRunComplete: Result: $($FeatureResult.Success)"
|
||
LogEntry "PhaseSageRunComplete: RestartNeeded: $($FeatureResult.RestartNeeded)"
|
||
LogEntry "Reboot Required: *** MANUAL REBOOT REQUIRED ***"
|
||
Exit
|
||
}
|
||
}
|
||
elseif($DEStatus -eq $false)
|
||
{
|
||
#DE removed, update status
|
||
LogEntry "PhaseSageRunComplete: DE Removed. Updating status..."
|
||
Set-ItemProperty -Path $ScriptRegKey -Name $ScriptRegValueName -Value $PhaseDERemoved
|
||
}
|
||
else
|
||
{
|
||
LogEntry "PhaseSageRunComplete: ERROR."
|
||
LogEntry $Error[0].Exception
|
||
}
|
||
$CurrentState = GetCurrentState
|
||
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry "PhaseSageRunComplete: Caught Exception."
|
||
LogEntry "$($Error[0].Exception)"
|
||
}
|
||
break
|
||
}
|
||
|
||
$PhaseDERemoved
|
||
{
|
||
try
|
||
{
|
||
#Retrieving initial space
|
||
$SpaceAtStart = (Get-ItemProperty -Path $ScriptRegKey -Name $ScriptSpaceBeforeValue)."$ScriptSpaceBeforeValue"
|
||
|
||
#remove reg key
|
||
LogEntry "PhaseDERemoved: Removing Script Reg Key."
|
||
Remove-Item $ScriptRegKey
|
||
$CurrentState = $PhaseTaskRemoved
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry "PhaseDERemoved: ERROR."
|
||
LogEntry $Error[0].Exception
|
||
}
|
||
break
|
||
}
|
||
|
||
|
||
}
|
||
|
||
#Prevents infinite loops consuming resources.
|
||
Sleep 1
|
||
|
||
} until ($CurrentState -eq $PhaseTaskRemoved)
|
||
|
||
if($CurrentState -eq $PhaseTaskRemoved)
|
||
{
|
||
try
|
||
{
|
||
LogEntry "PhaseTaskRemoved: Removing Scheduled Task"
|
||
DeleteScheduledTask -TaskName $SchTaskName
|
||
LogEntry "PhaseTaskRemoved: Scheduled Task Deleted."
|
||
LogEntry "PhaseTaskRemoved: Script Complete."
|
||
|
||
$CurrentSpace = (Get-WmiObject win32_logicaldisk | where { $_.DeviceID -eq $env:SystemDrive }).FreeSpace
|
||
LogEntry "PhaseTaskRemoved: Current Disk Space: $([Math]::Round(($CurrentSpace / 1GB),2)) GB"
|
||
|
||
$Savings = [Math]::Round(((($CurrentSpace / $SpaceAtStart) - 1) * 100),2)
|
||
|
||
$message = @"
|
||
****** CleanMgr complete.
|
||
****** Starting Free Space: $SpaceAtStart
|
||
****** Current Free Space: $CurrentSpace
|
||
****** Savings: $Savings%
|
||
****** Exiting.
|
||
"@
|
||
|
||
LogEntry $message
|
||
}
|
||
|
||
catch
|
||
{
|
||
LogEntry "PhaseTaskRemoved: Error during PhaseTaskRemoved..."
|
||
LogEntry $Error[0].Exception
|
||
}
|
||
|
||
Exit
|
||
} |