[CmdletBinding()] param( [switch]$SkipDiskRepair=$true, [switch]$PostRestart=$false ) ## MSP Name $MSPName = "Emberkom" ## Syncro Subdomain $Subdomain = $MSPName.ToLower() ## Directory to save copied script into $ScriptDirectory = "${Env:ProgramData}\${MSPName}\Scripts" ## Set the file name for this script when copying to $ScriptDirectory ## Note: the .ps1 file extension will be automatically added $ScriptName = "Fix-WindowsHealth" ## Directory to save log file(s) into $LogDirectory = "${Env:ProgramData}\${MSPName}\Logs" ## File to log messages into $LogFile = "${LogDirectory}\${ScriptName}.log" ## Function to write log messages to a file Function LogMessage([string]$log,[string]$msg) { If (! (Test-Path $log) ) { New-Item -Path $log -ItemType File -Force | Out-Null } Add-Content -Path $log -Value ("{0} - {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"),$msg) } ## Delete the script log file if it already exists If ( Test-Path $LogFile ) { Remove-Item -Path $LogFile -Force } ## Copy script locally and schedule chkdsk at next boot If ( ($PostRestart -eq $false) -and ($SkipDiskRepair -eq $false) ) { ## Copy this script so it can be run post-reboot If (! (Test-Path $ScriptDirectory) ) { New-Item -Path $ScriptDirectory -ItemType Directory -Force | Out-Null } $ScriptFile = "${ScriptDirectory}\${ScriptName}.ps1" If ( $ScriptFile -ne $MyInvocation.MyCommand.Definition ) { If ( Test-Path $ScriptFile ) { Remove-Item $ScriptFile -Force } Copy-Item -Path $MyInvocation.MyCommand.Definition -Destination $ScriptFile -Force If ( Test-Path $ScriptFile ) { LogMessage $LogFile "Successfully copied running script to: ${ScriptFile}" } Else { LogMessage $LogFile "Failed to copy running script to: ${ScriptFile}" } } ## Schedule chkdsk for next reboot & cmd.exe /c echo y `| chkdsk.exe /f /r /x LogMessage $LogFile "Scheduled chkdsk.exe for next reboot" ## Create a RunOnce task to resume this script after the disk has been repaired $command = """%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"" -ExecutionPolicy Bypass -File ""${ScriptFile}"" -PostRestart" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" -Name $ScriptName -Value $command If ( (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" -Name $ScriptName) ) { LogMessage $LogFile "Successfully scheduled post-reboot task: ${command}" } Else { LogMessage $LogFile "Failed to schedule post-reboot task" } } ## Get the last log entry from chkdsk If ( $PostRestart ) { ## Get the last event chkdsk recorded in the Event Log $chkdskmsg = Get-EventLog -LogName 'Application' -Source 'Wininit' -Newest 1 -ErrorAction SilentlyContinue If ( $chkdskmsg ) { ## Export the chkdsk log to a separate file for review $ChkdskLog = "${LogDirectory}\${ScriptName}-Chkdsk.log" If ( Test-Path $ChkdskLog ) { Remove-Item $ChkdskLog -Force } New-Item -Path $ChkdskLog -ItemType File -Value $chkdskmsg.Message.ToString() If ( Test-Path $ChkdskLog ) { LogMessage $LogFile "Successfully exported chkdsk result" } Else { LogMessage $LogFile "Failed to export chkdsk result" } } Else { LogMessage $LogFile "Last chkdsk result could not be obtained" } } ## Cleanup online image and restore health If ( $PostRestart -or $SkipDiskRepair ) { ## Run enhanced DISM for Windows 8 and higher assets $WindowsVersion = [double]("{0}.{1}" -f ([System.Environment]::OSVersion.Version).Major,([System.Environment]::OSVersion.Version).Minor) If ( $WindowsVersion -ge 6.2 ) { $BeginDISMRun = $(Get-Date).AddSeconds(-1) ## Scan the Windows Component Store for problems LogMessage $LogFile "Beginning Windows Component Store scan" Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /scanhealth" -Wait LogMessage $LogFile "Completed Windows Component Store scan" ## Check to see if the previous command recorded any problems that need repairing LogMessage $LogFile "Beginning Windows Component Store health check" $CheckHealthOutput = $(dism /online /cleanup-image /checkhealth) | Out-String If ( $CheckHealthOutput -match "repairable" ) { LogMessage $LogFile "Completed Windows Component Store health check: Problems found" ## Repair problems with the Windows Component Store LogMessage $LogFile "Beginning Windows Component Store restoration" Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait LogMessage $LogFile "Completed Windows Component Store restoration" } Else { LogMessage $LogFile "Completed Windows Component Store health check: No problems found" } $EndDISMRun = $(Get-Date).AddSeconds(1) ## Parse DISM log file for recent activity $DISMLogFile = "${LogDirectory}\${ScriptName}-DISM.log" If ( Test-Path $DISMLogFile ) { Remove-Item $DISMLogFile -Force } New-Item -Path $DISMLogFile -ItemType File LogMessage $LogFile "Beginning Windows Component Store log data collection" Get-Content -Path "${Env:WinDir}\Logs\DISM\dism.log" | Select-String -Pattern '^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})' | % { If ( ((Get-Date $_.Line.Substring(0,19)) -gt $BeginDISMRun) -and ((Get-Date $_.Line.Substring(0,19)) -le $EndDISMRun) ) { Add-Content -Path $DISMLogFile -Value $_.Line } } LogMessage $LogFile "Completed Windows Component Store log data collection" } Else { LogMessage $LogFile "Online image cleanup and restoration is only supported on Windows 8 and higher" } } ## Run SFC If ( $PostRestart -or $SkipDiskRepair ) { ## Start the TrustedInstaller service if it isn't running ## Note: this is necessary for the SFC utility to function properly If ( (Get-Service -Name TrustedInstaller).Status -ne "Running" ) { Restart-Service -Name TrustedInstaller -Force LogMessage $LogFile "Started TrustedInstaller service" } ## Start the SFC utility to check validity of Windows system files LogMessage $LogFile "Beginning SFC scan and fix" $BeginSFCRun = $(Get-Date).AddSeconds(-1) Start-Process "sfc" -ArgumentList "/scannow" -Wait $EndSFCRun = $(Get-Date).AddSeconds(1) LogMessage $LogFile "Completed SFC scan and fix" ## Parse the CBS.log file and export any SFC related messages $SFCLogFile = "${LogDirectory}\${ScriptName}-SFC.log" If ( Test-Path $SFCLogFile ) { Remove-Item $SFCLogFile -Force } New-Item -Path $SFCLogFile -ItemType File LogMessage $LogFile "Beginning SFC log data collection" Get-Content -Path "${Env:WinDir}\Logs\CBS\CBS.log" | Select-String -Pattern '^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}).+\[SR\]' | % { If ( ((Get-Date $_.Line.Substring(0,19)) -gt $BeginSFCRun) -and ((Get-Date $_.Line.Substring(0,19)) -le $EndSFCRun) ) { Add-Content -Path $SFCLogFile -Value $_.Line } } LogMessage $LogFile "Completed SFC log data collection" } ## Prepare Log File Upload