Public/Invoke-InfraPulse.ps1

function Invoke-InfraPulse {
    <#
    .SYNOPSIS
        Runs read-only infrastructure health checks.

    .DESCRIPTION
        Evaluates one or more hosts locally or through PowerShell remoting. The command returns structured InfraPulse.Report objects and does not change target state.

        Windows-specific checks use built-in CIM, service, certificate, and event-log interfaces. DNS, TCP, and SNTP checks can run on any target that supports the required .NET APIs.

    .PARAMETER ComputerName
        One or more target computer names. Localhost is used by default. Remote targets use WSMan PowerShell remoting.

    .PARAMETER Session
        Existing PSSession objects. InfraPulse never closes sessions supplied by the caller.

    .PARAMETER ConfigurationPath
        Path to a .psd1 configuration file. When neither ConfigurationPath nor Configuration is supplied, InfraPulse discovers a configuration from the INFRAPULSE_CONFIG environment variable or an infra-pulse.psd1 file in the working directory before falling back to built-in defaults; the report records the effective source in ConfigurationSource.

    .PARAMETER Configuration
        Configuration overrides supplied as a hashtable.

    .PARAMETER Check
        Runs only the selected checks. An explicit selection overrides each check's Enabled value.

    .PARAMETER Credential
        Credential used when InfraPulse creates remote sessions. Credentials are not persisted.

    .PARAMETER Authentication
        PowerShell remoting authentication mechanism.

    .PARAMETER UseSSL
        Uses HTTPS for WSMan remoting.

    .PARAMETER Port
        Overrides the remoting port.

    .PARAMETER ThrottleLimit
        Maximum number of computers scanned concurrently. Multiple targets are scanned in parallel runspaces unless ThrottleLimit is 1 or FailFast is set, which force sequential processing.

    .PARAMETER FailFast
        Stops at the first connection or check failure instead of returning a control result and continuing. Implies sequential processing.

    .PARAMETER Tag
        Adds user-defined tags to every returned report.

    .EXAMPLE
        Invoke-InfraPulse

        Runs the enabled default checks against the local computer.

    .EXAMPLE
        $report = Invoke-InfraPulse -ComputerName 'srv-app-01' -ConfigurationPath .\infra-pulse.psd1 -Credential (Get-Credential)

        Connects to a remote host through WSMan and applies a configuration file.

    .EXAMPLE
        Get-PSSession | Invoke-InfraPulse -Check Disk, Memory, PendingReboot

        Reuses caller-owned sessions and runs an explicit subset of checks.

    .EXAMPLE
        Invoke-InfraPulse -ComputerName 'srv-01', 'srv-02' | Export-InfraPulseReport -Path .\out\health.html -Force

        Scans two hosts and writes one self-contained HTML report.
    #>

    [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
    param(
        [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('CN', 'Name', 'DNSHostName')]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = @('localhost'),

        [Parameter(Mandatory, ParameterSetName = 'Session', ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.PSSession[]]$Session,

        [ValidateNotNullOrEmpty()]
        [string]$ConfigurationPath,

        [ValidateNotNull()]
        [System.Collections.IDictionary]$Configuration,

        [ValidateSet('Disk', 'Memory', 'Uptime', 'PendingReboot', 'PatchAge', 'Services', 'Certificates', 'EventLog', 'Dns', 'Tcp', 'Tls', 'TimeSync')]
        [string[]]$Check,

        [Parameter(ParameterSetName = 'ComputerName')]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateSet('Default', 'Basic', 'Negotiate', 'NegotiateWithImplicitCredential', 'Credssp', 'Digest', 'Kerberos')]
        [string]$Authentication = 'Default',

        [Parameter(ParameterSetName = 'ComputerName')]
        [switch]$UseSSL,

        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateRange(1, 65535)]
        [int]$Port,

        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateRange(1, 64)]
        [int]$ThrottleLimit = 8,

        [switch]$FailFast,

        [string[]]$Tag = @()
    )

    begin {
        $resolved = Resolve-InfraPulseConfiguration -ConfigurationPath $ConfigurationPath -Configuration $Configuration
        $resolvedConfiguration = $resolved.Configuration
        $configurationSource = [string]$resolved.Source
        $environmentName = [string]$resolvedConfiguration.General.EnvironmentName
        $runId = [guid]::NewGuid().ToString()
        $configurationFingerprint = Get-InfraPulseConfigurationFingerprint -Configuration $resolvedConfiguration
        $pipelineTargets = New-Object System.Collections.Generic.List[string]
        $catalog = @(Get-InfraPulseCheckCatalog)
        $normalizedTags = @(
            $Tag |
                Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } |
                ForEach-Object { ([string]$_).Trim() } |
                Select-Object -Unique
        )

        if ($PSBoundParameters.ContainsKey('Check')) {
            $selectedChecks = @($Check | Select-Object -Unique)
        }
        else {
            $selectedChecks = @(
                foreach ($defaultCheck in @($resolvedConfiguration.General.DefaultChecks)) {
                    $definition = $catalog | Where-Object { $_.Name -eq [string]$defaultCheck } | Select-Object -First 1
                    if ($null -ne $definition -and [bool]$resolvedConfiguration.Checks[$definition.Name].Enabled) {
                        $definition.Name
                    }
                }
            )
        }
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Session') {
            foreach ($currentSession in $Session) {
                $requestedName = [string]$currentSession.ComputerName
                if ([string]::IsNullOrWhiteSpace($requestedName)) {
                    $requestedName = [string]$currentSession.InstanceId
                }

                if ($currentSession.State -ne 'Opened') {
                    $message = "PSSession state is '$($currentSession.State)'."
                    if ($FailFast -or -not [bool]$resolvedConfiguration.General.ContinueOnError) {
                        throw "Cannot use session for '$requestedName': $message"
                    }
                    New-InfraPulseConnectionFailureReport -ComputerName $requestedName -ErrorMessage $message -Tags $normalizedTags -RunId $runId -ConfigurationFingerprint $configurationFingerprint -ConfigurationSource $configurationSource -EnvironmentName $environmentName
                    continue
                }

                $context = [pscustomobject]@{
                    RequestedComputerName = $requestedName
                    ComputerName          = $requestedName
                    Session               = $currentSession
                    OwnsSession           = $false
                }

                try {
                    Invoke-InfraPulseTarget -Context $context -Configuration $resolvedConfiguration -Checks $selectedChecks -FailFast:$FailFast -Tags $normalizedTags -RunId $runId -ConfigurationFingerprint $configurationFingerprint -ConfigurationSource $configurationSource -EnvironmentName $environmentName
                }
                catch {
                    if ($FailFast -or -not [bool]$resolvedConfiguration.General.ContinueOnError) {
                        throw
                    }
                    New-InfraPulseExecutionFailureReport -RequestedComputerName $requestedName -ComputerName $context.ComputerName -ErrorMessage $_.Exception.Message -Tags $normalizedTags -RunId $runId -ConfigurationFingerprint $configurationFingerprint -ConfigurationSource $configurationSource -EnvironmentName $environmentName
                }
            }
        }
        else {
            foreach ($target in $ComputerName) {
                [void]$pipelineTargets.Add([string]$target)
            }
        }
    }

    end {
        if ($PSCmdlet.ParameterSetName -eq 'Session') {
            return
        }

        $targets = @($pipelineTargets.ToArray())
        if ($targets.Count -eq 0) {
            return
        }

        $effectivePort = if ($PSBoundParameters.ContainsKey('Port')) { $Port } else { 0 }
        $targetParameters = @{
            Configuration            = $resolvedConfiguration
            Checks                   = $selectedChecks
            Tags                     = $normalizedTags
            RunId                    = $runId
            ConfigurationFingerprint = $configurationFingerprint
            ConfigurationSource      = $configurationSource
            EnvironmentName          = $environmentName
            Authentication           = $Authentication
            UseSSL                   = [bool]$UseSSL
            Port                     = $effectivePort
            FailFast                 = [bool]$FailFast
        }
        if ($null -ne $Credential) {
            $targetParameters.Credential = $Credential
        }

        # FailFast requires deterministic early termination, so it always runs
        # sequentially; parallel runspaces only pay off for multiple targets.
        $useParallel = $targets.Count -gt 1 -and $ThrottleLimit -gt 1 -and -not $FailFast
        if (-not $useParallel) {
            foreach ($target in $targets) {
                Invoke-InfraPulseComputerTarget -TargetName $target @targetParameters
            }
            return
        }

        $initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
        [void]$initialSessionState.ImportPSModule($script:InfraPulseManifestPath)
        $runspacePool = [runspacefactory]::CreateRunspacePool(1, $ThrottleLimit, $initialSessionState, $Host)
        $runspacePool.Open()
        $workers = New-Object System.Collections.Generic.List[object]

        try {
            foreach ($target in $targets) {
                $workerParameters = @{} + $targetParameters
                $workerParameters.TargetName = [string]$target

                $worker = [powershell]::Create()
                $worker.RunspacePool = $runspacePool
                [void]$worker.AddScript('param($WorkerParameters) & (Get-Module -Name InfraPulse) { param($p) Invoke-InfraPulseComputerTarget @p } $WorkerParameters').AddArgument($workerParameters)
                $workerRecord = @{
                    Target     = [string]$target
                    PowerShell = $worker
                    Handle     = $worker.BeginInvoke()
                }
                [void]$workers.Add($workerRecord)
            }

            # Results are emitted in input order; workers that finish ahead of
            # the cursor simply wait in their runspaces.
            foreach ($workerEntry in @($workers.ToArray())) {
                try {
                    $workerEntry.PowerShell.EndInvoke($workerEntry.Handle)
                }
                catch {
                    New-InfraPulseExecutionFailureReport -RequestedComputerName ([string]$workerEntry.Target) -ComputerName ([string]$workerEntry.Target) -ErrorMessage $_.Exception.Message -Tags $normalizedTags -RunId $runId -ConfigurationFingerprint $configurationFingerprint -ConfigurationSource $configurationSource -EnvironmentName $environmentName
                }
                finally {
                    $workerEntry.PowerShell.Dispose()
                }
            }
        }
        finally {
            $runspacePool.Close()
            $runspacePool.Dispose()
        }
    }
}