diff --git a/Fix-WindowsHealth(old).ps1 b/Fix-WindowsHealth(old).ps1 new file mode 100644 index 0000000..a2177fd --- /dev/null +++ b/Fix-WindowsHealth(old).ps1 @@ -0,0 +1,137 @@ +[CmdletBinding()] +param( + [switch]$SkipDiskRepair=$true, + [switch]$PostRestart=$false +) + +## Set the file name for this script when copying to $ScriptDirectory +## Note: the .ps1 file extension will be automatically added +$ScriptName = "Fix-WindowsHealth" + +## File to log messages into +$LogFile = "${MSPLogs}\${ScriptName}.log" + +## 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 ) { + Log $LogFile "Successfully copied running script to: ${ScriptFile}" + } Else { Log $LogFile "Failed to copy running script to: ${ScriptFile}" } + } + + ## Schedule chkdsk for next reboot + & cmd.exe /c echo y `| chkdsk.exe /f /r /x + Log $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) ) { + Log $LogFile "Successfully scheduled post-reboot task: ${command}" + } Else { Log $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 ) { + Log $LogFile "Successfully exported chkdsk result" + } Else { Log $LogFile "Failed to export chkdsk result" } + + } Else { Log $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 + If ( IsWindowsVersion -ge "6.2" ) { + + $BeginDISMRun = $(Get-Date).AddSeconds(-1) + + ## Scan the Windows Component Store for problems + Log $LogFile "Beginning Windows Component Store scan" + Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /scanhealth" -Wait + Log $LogFile "Completed Windows Component Store scan" + + ## Check to see if the previous command recorded any problems that need repairing + Log $LogFile "Beginning Windows Component Store health check" + $CheckHealthOutput = $(dism /online /cleanup-image /checkhealth) | Out-String + If ( $CheckHealthOutput -match "repairable" ) { + + Log $LogFile "Completed Windows Component Store health check: Problems found" + + ## Repair problems with the Windows Component Store + Log $LogFile "Beginning Windows Component Store restoration" + Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait + Log $LogFile "Completed Windows Component Store restoration" + } Else { Log $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 + Log $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 + } + } + + Log $LogFile "Completed Windows Component Store log data collection" + + } Else { Log $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 + Log $LogFile "Started TrustedInstaller service" + } + + ## Start the SFC utility to check validity of Windows system files + Log $LogFile "Beginning SFC scan and fix" + + $BeginSFCRun = $(Get-Date).AddSeconds(-1) + Start-Process "sfc" -ArgumentList "/scannow" -Wait + $EndSFCRun = $(Get-Date).AddSeconds(1) + Log $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 + Log $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 + } + } + Log $LogFile "Completed SFC log data collection" +} + +## Prepare Log File Upload diff --git a/Fix-WindowsHealth.ps1 b/Fix-WindowsHealth.ps1 index d4d98c8..a5de3eb 100644 --- a/Fix-WindowsHealth.ps1 +++ b/Fix-WindowsHealth.ps1 @@ -1,159 +1,41 @@ -[CmdletBinding()] -param( - [switch]$SkipDiskRepair=$true, - [switch]$PostRestart=$false -) +## Run enhanced DISM for Windows 8 and higher endpoints +If ( IsWindowsVersion -ge "6.2" ) +{ + $LogFile = "${MSPLogs}\Fix-WindowsHealth.log" -## MSP Name -$MSPName = "Emberkom" + $BeginDISMRun = $(Get-Date).AddSeconds(-1) -## Syncro Subdomain -$Subdomain = $MSPName.ToLower() + ## Scan the Windows Component Store for problems + Log "Beginning Windows Component Store scan" $LogFile + Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /scanhealth" -Wait + Log "Completed Windows Component Store scan" $LogFile -## 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" ) { + ## Check to see if the previous command recorded any problems that need repairing + Log "Beginning Windows Component Store health check" $LogFile + $CheckHealthOutput = $(dism /online /cleanup-image /checkhealth) | Out-String + If ( $CheckHealthOutput -match "repairable" ) + { + Log "Completed Windows Component Store health check: Problems found" $LogFile - 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" + ## Repair problems with the Windows Component Store + Log "Beginning Windows Component Store restoration" $LogFile + Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait + Log "Completed Windows Component Store restoration" $LogFile } - - ## Start the SFC utility to check validity of Windows system files - LogMessage $LogFile "Beginning SFC scan and fix" + Else { Log "Completed Windows Component Store health check: No problems found" $LogFile } - $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 + $EndDISMRun = $(Get-Date).AddSeconds(1) + + ## Parse DISM log file for recent activity + $DISMLogFile = "${MSPLogs}\Fix-WindowsHealth_DISM.log" + If ( Test-Path $DISMLogFile ) { Remove-Item $DISMLogFile -Force } + New-Item -Path $DISMLogFile -ItemType File + Log "Beginning Windows Component Store log data collection" $LogFile + 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 SFC log data collection" + Log "Completed Windows Component Store log data collection" $LogFile } - -## Prepare Log File Upload +Else { Log "Online image cleanup and restoration is only supported on Windows 8 and higher" $LogFile } \ No newline at end of file diff --git a/Tools.ps1 b/Tools.ps1 index a31b3da..dcaa2b1 100644 --- a/Tools.ps1 +++ b/Tools.ps1 @@ -1,48 +1,64 @@ -## Function to log activity to the configured log directory -Function global:Log -{ +## Define some basic variables +$MSPName = "Emberkom" +## Setup the MSP management directories +If ( Test-Path ${Env:ProgramData} ) +{ $MSPRoot = "${Env:ProgramData}\${MSPName}" } Else { $MSPRoot = "${Env:SystemDrive}\${MSPName}" } +$MSPScripts = "${MSPRoot}\Scripts" +$MSPTools = "${MSPRoot}\Tools" +$MSPLogs = "${MSPRoot}\Logs" + +## Set a datestamp for any log files that might need to be created +$LogFilePrefix = Get-Date -Format "yyyyMMddHHmmss" + +## Make sure all the management directories exist +If ( !(Test-Path $MSPRoot) ) +{ New-Item -Path $MSPRoot -ItemType Directory -Force | Out-Null } +If ( !(Test-Path $MSPScripts) ) +{ New-Item -Path $MSPScripts -ItemType Directory -Force | Out-Null } +If ( !(Test-Path $MSPTools) ) +{ New-Item -Path $MSPTools -ItemType Directory -Force | Out-Null } +If ( !(Test-Path $MSPLogs) ) +{ New-Item -Path $MSPLogs -ItemType Directory -Force | Out-Null } + +## Function to log activity to the configured log directory +Function Log +{ +param([string]$Message,[string]$LogName) +$LogEntry = "{0} - {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"),$Message +If ($LogName ) +{ + $LogFile = "${MSPLogs}\${LogFilePrefix}-${LogName}.log" + If (! (Test-Path $LogFile) ) { New-Item -Path $LogFile -ItemType File -Force | Out-Null } + Add-Content -Path $LogFile -Value $LogEntry +} +Write-Output $LogEntry } ## Function to determine if the current user context is an Administrator -Function global:IsAdmin +Function IsAdmin { If ( ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) ) { Return $true } Else { Return $false } } ## Runs a script from the repo -Function global:Run-PSScript +Function Run-PSScript { -param( - [string]$Type='management-scripts', - [string]$Name, - [string]$Arguments) - -## Get rid of null arguments +param([string]$Type='management-scripts',[string]$Name,[string]$Arguments) If ( $Arguments -eq "null" ) { $Arguments = $null } - -## Make sure $Name and $Type are defined If ( $Name -and $Type ) { - ## Form the URL to the specified script $URL = "https://dev.emberkom.com/emberkom/${Type}/raw/branch/master/${Name}.ps1" - - ## Get the script Write-Output "Retrieving : ${Name}.ps1" Try { $Script = (New-Object Net.WebClient).DownloadString($URL) } Catch { Write-Output $_.Exception.Message } - - ## Make a script block Try { $ScriptBlock = [Scriptblock]::Create($Script) } Catch { Write-Output $_.Exception.Message } - - ## Run the script any any arguments Write-Output "Executing: ${Name}.ps1 ${Arguments}" Try { Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $Arguments } Catch { Write-Output $_.Exception.Message } -} -Else { Write-Output "No script or type was specified" } +} Else { Write-Output "No script or type was specified" } } ## Function to return a Powershell version object from a string @@ -99,4 +115,7 @@ switch ($PSCmdlet.ParameterSetName) 'le' { If ( $WindowsBuild -le $le ) { Return $true } Else { Return $false } } 'eq' { If ( $WindowsBuild -eq $eq ) { Return $true } Else { Return $false } } } -} \ No newline at end of file +} + +Function Write([string]$msg) +{ Write-Output "Called by: ${msg}" } \ No newline at end of file