systemchecks.psm1
|
#Requires -Version 5 <# .Synopsis Get a file count from a given path. .Description Get a file count from a given path. Can handle network shares as well. .Parameter .Example Get-FileCount FilePath "c:\my\file" #> function Get-FileCount { [CmdletBinding()] param ( [string]$FilePath, [string]$SystemName, [string]$SystemDescription, [string]$AppendLeaf, [string]$LeafFormat ) $HealthCheckType = 'FileCount' try { if (Test-Path -Path $FilePath) { $FolderName = Split-Path -Path $FilePath -Leaf if (-not [string]::IsNullOrEmpty($AppendLeaf)) { switch ($AppendLeaf) { 'Today' { $ChildPath = Get-Date -Format $LeafFormat $CheckPath = Join-Path -Path $FilePath -ChildPath $ChildPath $FolderName += "\$ChildPath." } 'Yesterday' { $ChildPath = Get-Date -Date ((Get-Date).AddDays(-1)) -Format $LeafFormat $CheckPath = Join-Path -Path $FilePath -ChildPath $ChildPath $FolderName += "\$ChildPath." } } Write-Verbose "AppendLeaf: $AppendLeaf" Write-Verbose "CheckPath: $CheckPath" Write-Verbose "FolderName: $FolderName" } else { $CheckPath = $FilePath Write-Verbose "Skipping - AppendLeaf" } if (Test-Path -Path $CheckPath) { $FileCount = Get-ChildItem -Path $CheckPath -File | Measure-Object | Select-Object -ExpandProperty Count $Comment = $CheckPath Write-Verbose "Good test path - value: $CheckPath" } else{ $Exception = $Error[0].Exception.Message if ([string]::IsNullOrEmpty($Exception)) { $Exception = "Path not found: $CheckPath" } Write-Verbose $Exception $FileCount = 0 $Comment = $Exception } Write-Verbose "Comment: $Comment" return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $FolderName Type = $HealthCheckType Status = $FileCount LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Comment ComputerName = $ENV:COMPUTERNAME } } else { $Exception = $Error[0].Exception.Message if ([string]::IsNullOrEmpty($Exception)) { $Exception = "Path not found: $FilePath" } Write-Verbose $Exception return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $FilePath Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Exception ComputerName = $ENV:COMPUTERNAME } } } catch { $Exception = $Error[0].Exception.Message if ([string]::IsNullOrEmpty($Exception)) { $Exception = "Path not found: $FilePath" } Write-Verbose $Exception return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $FilePath Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Exception ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis Analyze different components of a system. .Description Analyze different components of a system. A system can be any collection of items to check. .Parameter .Example #> function Get-SystemHealth { [CmdletBinding()] param ( [System.Object[]]$ConfigFileName ) $ScriptDirectory = Split-Path $Script:MyInvocation.MyCommand.Path -Parent $IncludesPath = Join-Path -Path $ScriptDirectory -ChildPath "Includes" . (Join-Path -Path $IncludesPath -ChildPath "Get-Win32Error.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-FileExists.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-ShareExists.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-ProcessHealth.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-ScheduledTask.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-ServiceHealth.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-TimeSync.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Test-URIHealth.ps1") . (Join-Path -Path $IncludesPath -ChildPath "Get-FileCount.ps1") $ConfigFileName | ForEach-Object { $ConfigFile = $_ $SystemHealthData = @() $file = Get-Content -Path $ConfigFile.FullName | ConvertFrom-Json $SystemName = $file.systemName $SystemDescription = $file.description $file.Processes | ForEach-Object { $procSplat = @{ ProcessName = $_.name SystemName = $SystemName SystemDescription = $SystemDescription } $SystemHealthData += Test-ProcessHealth @procSplat } $file.Services | ForEach-Object { $serviceSplat = @{ ServiceName = $_.name SystemName = $SystemName SystemDescription = $SystemDescription } $SystemHealthData += Test-ServiceHealth @serviceSplat } $file.FilesExist | ForEach-Object { $checkfileSplat = @{ FilePath = $_.FilePath SystemName = $SystemName SystemDescription = $SystemDescription } $SystemHealthData += Test-FileExists @checkfileSplat } $file.SharesExist | ForEach-Object { $checkshareSplat = @{ SharePath = $_.SharePath SystemName = $SystemName SystemDescription = $SystemDescription } $SystemHealthData += Test-ShareExists @checkshareSplat $File.URIs | ForEach-Object { $checkURISplat = @{ URI = $_.URI SystemName = $SystemName SystemDescription = $SystemDescription UseBasicParsing = $_.useBasicParsing UseDefaultCredentials = $_.useDefaultCredentials } $SystemHealthData += Test-URIHealth @checkURISplat } $file.ScheduledTasks | ForEach-Object { $schedtaskSplat = @{ TaskPath = $_.TaskPath SystemName = $SystemName SystemDescription = $SystemDescription } $SystemHealthData += Test-ScheduledTask @schedtaskSplat } $file.FileCount | ForEach-Object { $filecountSplat = @{ FilePath = $_.FilePath SystemName = $SystemName SystemDescription = $SystemDescription AppendLeaf = $_.appendLeaf LeafFormat = $_.leafFormat } $SystemHealthData += Get-FileCount @filecountSplat } $OutFileName = ".\output_files\healthcheck_$($ENV:COMPUTERNAME)_$($ConfigFile.Name)" $SystemHealthData | ConvertTo-Json | Out-File (New-Item -Path $OutFileName -Force) } $SystemHealthData } } #Requires -Version 5 <# .Synopsis This function calls the error lookup tool to get detailed information about an error. .Description This function is intended to be used with Get-SystemHealth and relies on $ScriptDirectory for proper function. Requires the Microsoft Error Lookup Tool (err.exe), included in the project. For details on error lookup tool see article: https://www.microsoft.com/en-us/download/details.aspx?id=100432&msockid=2fd802363d216c82121f16d63c406d64 The tool can also be installed by using winget (winget install Microsoft.err). This function expects the tool is NOT installed and runs it from the project folder. NOTE: By default, this function checks errors against the winerror.h file. .Parameter .Example Get-Win32Error 0x80070005 # Access Denied error #> function Get-Win32Error { [CmdletBinding()] param( [Parameter(Mandatory)] [int]$ErrorCode ) # Path to the Error Lookup Tool executable $errExe = Join-Path -Path $ScriptDirectory -ChildPath "Includes\err.exe" Write-Verbose "Error Tool Path: {$errExe}" # Check if the executable exists if (!(Test-Path $errExe)) { Write-Error "Error Lookup Tool not found at $errExe" return } # Run the tool and capture output $output = & $errExe "/winerror.h" $ErrorCode # Parse the output and return the message # this is a rough parse if ($output -match "winerror.h") { $returnvalue = $output -join " " $returnvalue = $returnvalue -replace "#", '`r`n' return $returnvalue } else { return "Error code not found" } } #Requires -Version 5 <# .Synopsis See if a file path exists. .Description See if a file path exists. .Parameter .Example Test-FileExists -FilePath "c:\my\file" #> function Test-FileExists { [CmdletBinding()] param ( [string]$FilePath, [string]$SystemName, [string]$SystemDescription ) $HealthCheckType = 'FileExists' $filename = Split-Path -Path $FilePath -Leaf try { if (Test-Path -Path $FilePath) { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $filename Type = $HealthCheckType Status = 'Exists' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $FilePath ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $filename Type = $HealthCheckType Status = 'Not Found' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $FilePath ComputerName = $ENV:COMPUTERNAME } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $FilePath Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Error[0].Exception.Message ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis See if a process is running. .Description See if a process is running. .Parameter .Example Test-ProcessHealth -ProcessName "explorer" #> function Test-ProcessHealth { [CmdletBinding()] param ( [string]$ProcessName, [string]$SystemName, [string]$SystemDescription ) $HealthCheckType = 'Process' try { $process = Get-Process -Name $ProcessName -ErrorAction Stop if ($process.Responding -eq $true) { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ProcessName Type = $HealthCheckType Status = 'Responding' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "" ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ProcessName Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "Process not responding. Status: $($process.Responding)" ComputerName = $ENV:COMPUTERNAME } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ProcessName Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $_.Exception.Message ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis Get status of a scheduled task. .Description Get status of a scheduled task. .Parameter .Example Test-ScheduledTask -TaskPath "\SLMPD\Send OnCallSchedule" #> function Test-ScheduledTask { [CmdletBinding()] param ( [string]$TaskPath, [string]$SystemName, [string]$SystemDescription ) $HealthCheckType = 'ScheduledTask' $task = Split-Path -Path $TaskPath -Leaf $path = Split-Path -Path $TaskPath -Parent try { $taskdetail = Get-ScheduledTaskInfo -TaskName $task -TaskPath $path -ErrorAction Stop if ($taskdetail -and $taskdetail.LastTaskResult -eq '0') { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $TaskPath Type = $HealthCheckType Status = 'OK' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "LastRunTime: $($taskdetail.LastRunTime) NextRunTime: $($taskdetail.NextRunTime) MissedRuns: $($taskdetail.NumberOfMissedRuns)" ComputerName = $ENV:COMPUTERNAME } } else { if (!$null -eq $taskdetail) { $errorResult = Get-Win32Error -ErrorCode $taskdetail.LastTaskResult $parsedErrorMessage = $errorResult -split '`r`n' Write-Verbose "Parsed Error: {$parsedErrorMessage}" if (-not [string]::IsNullOrEmpty($parsedErrorMessage)) { $errMessage = $parsedErrorMessage[2].Trim() $errCodeConverted = $parsedErrorMessage[1].Trim() -replace '\s{2,}', ', ' } else { $errMessage = "" $errCodeConverted = 0 } return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $TaskPath Type = $HealthCheckType Status = ("{0} - {1}" -f $taskdetail.LastTaskResult, $errCodeConverted) LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $errMessage ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $TaskPath Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "Task not found." ComputerName = $ENV:COMPUTERNAME } } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $TaskPath Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "Task not found." ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis Get status of a windows service. .Description Get status of a windows service. .Parameter .Example Test-ServiceHealth -ServiceName 'w3svc' #> function Test-ServiceHealth { [CmdletBinding()] param ( [string]$ServiceName, [string]$SystemName, [string]$SystemDescription ) $HealthCheckType = 'Service' try { $service = Get-Service -Name $ServiceName -ErrorAction Stop if ($service.Status -eq 'Running') { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ServiceName Type = $HealthCheckType Status = 'OK' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "" ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ServiceName Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $service.Status ComputerName = $ENV:COMPUTERNAME } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ServiceName Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $_.Exception.Message ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis See if a share path exists. .Description See if a share path exists. .Parameter .Example Test-FileShare -SharePath "\\tapp1\e$" #> function Test-ShareExists { [CmdletBinding()] param ( [string]$SharePath, [string]$SystemName, [string]$SystemDescription ) $HealthCheckType = 'ShareExists' # Extract share name - handle UNC paths, drive letters, and regular paths if ($SharePath -match '^\\\\[^\\]+\\([^\\]+)') { # UNC path like \\server\share $ShareName = $matches[1] } elseif ($SharePath -match '^([A-Z]:)') { # Drive letter like C: $ShareName = $matches[1] } else { # Fall back to GetFileName for other paths $ShareName = [System.IO.Path]::GetFileName($SharePath) if ([string]::IsNullOrEmpty($ShareName)) { $ShareName = $SharePath } } try { if (Test-Path -Path $SharePath) { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ShareName Type = $HealthCheckType Status = 'Exists' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $SharePath ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ShareName Type = $HealthCheckType Status = 'Not Found' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $SharePath ComputerName = $ENV:COMPUTERNAME } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $ShareName Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Error[0].Exception.Message ComputerName = $ENV:COMPUTERNAME } } } #Requires -Version 5 <# .Synopsis Analyze different components of a system. .Description Analyze different components of a system. A system can be any collection of items to check. .Parameter .Example Test-TimeSync -System1Name "netviewer1" -System2Name "cad1" -Verbose #> function Test-TimeSync { [CmdletBinding()] param ( [string]$System1Name, [string]$System2Name ) $HealthCheckType = 'TimeSync' $session1 = New-PSSession -ComputerName $System1Name -ErrorAction SilentlyContinue if ($session1) { Write-Verbose "Collecting current date/time from $System1Name" $timedate = Invoke-Command -Session $session1 -ScriptBlock { Get-Date } $System1DateTime = [PSCustomObject]@{ SystemName = $System1Name SystemDateTime = $timedate Status = 'Success' Comment = '' } } else { $System1DateTime = [PSCustomObject]@{ SystemName = $System1Name SystemDateTime = '' Status = 'ERROR' Comment = "Unable to establish a session with '$System1Name'" } } $session2 = New-PSSession -ComputerName $System2Name -ErrorAction SilentlyContinue if ($session2) { Write-Verbose "Collecting current date/time from $System2Name" $timedate = Invoke-Command -Session $session2 -ScriptBlock { Get-Date } $System2DateTime = [PSCustomObject]@{ SystemName = $System2Name SystemDateTime = $timedate Status = 'Success' Comment = '' } } else { $System2DateTime = [PSCustomObject]@{ SystemName = $System2Name SystemDateTime = '' Status = 'ERROR' Comment = "Unable to establish a session with '$System2Name'" } } if ($System1DateTime.Status -eq 'error' -or $System2DateTime.Status -eq 'error') { $DateTimeDifference = 0 $Status = "ERROR" } else { $difference = New-TimeSpan -Start $System1DateTime.SystemDateTime -End $System2DateTime.SystemDateTime $DateTimeDifference = $difference $Status = 'Success' } $System1DateTime | Format-Table -AutoSize | Out-String | Write-Verbose $System2DateTime | Format-Table -AutoSize | Out-String | Write-Verbose return [PSCustomObject]@{ System1Name = $System1Name System1DateTime = $System1DateTime.SystemDateTime System2Name = $System2Name System2DateTime = $System2DateTime.SystemDateTime Type = $HealthCheckType Status = $Status Difference = $DateTimeDifference LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = @($System1DateTime.Comment,$System2DateTime.Comment) } } #Requires -Version 5 <# .Synopsis Get status of a given URI. .Description Get status of a given URI. .Parameter .Example Test-URIHealth -URI "\SLMPD\Send OnCallSchedule" #> function Test-URIHealth { [CmdletBinding()] param ( [string]$URI, [string]$SystemName, [string]$SystemDescription, [bool]$UseBasicParsing, [bool]$UseDefaultCredentials ) $HealthCheckType = 'URI' try { $WebRequestSplat = @{ URI = $URI UseBasicParsing = $UseBasicParsing UseDefaultCredentials = $UseDefaultCredentials } $response = Invoke-WebRequest @WebRequestSplat if (!$null -eq $response) { if ($response.StatusCode -eq '200') { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $URI Type = $HealthCheckType Status = 'OK' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = "" ComputerName = $ENV:COMPUTERNAME } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $URI Type = $HealthCheckType Status = $response.StatusDescription LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $response.StatusCode ComputerName = $ENV:COMPUTERNAME } } } else { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $URI Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $Error[0].Exception.Message ComputerName = $ENV:COMPUTERNAME } } } catch { return [PSCustomObject]@{ SystemName = $SystemName SystemDescription = $SystemDescription Name = $URI Type = $HealthCheckType Status = 'ERROR' LastUpdate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Comment = $_.Exception.Message ComputerName = $ENV:COMPUTERNAME } } } |