Public/windowsupdate/Show-WindowsUpdate.ps1

#Requires -Version 5.1
function Show-WindowsUpdate {
    <#
        .SYNOPSIS
            Unhides previously hidden Windows Updates to allow installation
 
        .DESCRIPTION
            Reverses the hiding of Windows Updates by setting the IsHidden property
            to false on matching IUpdate COM objects. This function searches only
            hidden, non-installed updates and restores visibility for those matching
            the specified KB article IDs.
            Use this after Hide-WindowsUpdate to re-enable updates for installation.
 
        .PARAMETER ComputerName
            One or more computer names to target. Defaults to the local computer.
            Accepts pipeline input by value and by property name.
 
        .PARAMETER Credential
            Optional PSCredential for authenticating to remote computers.
            Not required for local operations.
 
        .PARAMETER KBArticleID
            One or more KB article IDs to unhide. Accepts values with or without
            the KB prefix (e.g., 'KB5034441' or '5034441').
 
        .PARAMETER MicrosoftUpdate
            When specified, queries the full Microsoft Update catalog instead of the
            machine's configured source.
 
        .EXAMPLE
            Show-WindowsUpdate -KBArticleID 'KB5034441'
 
            Unhides KB5034441 on the local computer.
 
        .EXAMPLE
            Show-WindowsUpdate -ComputerName 'SRV01' -KBArticleID 'KB5034441', 'KB5035432'
 
            Unhides two updates on SRV01.
 
        .EXAMPLE
            'SRV01', 'SRV02' | Show-WindowsUpdate -KBArticleID 'KB5034441'
 
            Unhides KB5034441 on SRV01 and SRV02 via pipeline.
 
        .OUTPUTS
            PSWinOps.WindowsUpdateShowResult
            Returns objects with ComputerName, Title, KBArticle, Result, and Timestamp.
            Result is one of: Shown, NotFound.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-04-08
            Requires: PowerShell 5.1+ / Windows only
            Requires: Administrator privileges
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdate-get_ishidden
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType('PSWinOps.WindowsUpdateShowResult')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$KBArticleID,

        [Parameter(Mandatory = $false)]
        [switch]$MicrosoftUpdate
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"

        $normalizedKBIds = $KBArticleID | ForEach-Object -Process { $_ -replace '^KB', '' }
        $kbCsv = $normalizedKBIds -join ','
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] KB targets: $($normalizedKBIds | ForEach-Object -Process { "KB$_" })"

        $showScriptBlock = {
            param(
                [string]$KBCsv,
                [bool]$UseMicrosoftUpdate
            )

            $kbTargets = $KBCsv -split ','
            $session = New-Object -ComObject 'Microsoft.Update.Session'
            $session.ClientApplicationID = 'PSWinOps'
            $searcher = $session.CreateUpdateSearcher()

            if ($UseMicrosoftUpdate) {
                $serviceManager = New-Object -ComObject 'Microsoft.Update.ServiceManager'
                $serviceManager.ClientApplicationID = 'PSWinOps'
                $service = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '')
                $searcher.ServerSelection = 3
                $searcher.ServiceID = $service.ServiceID
            }

            # Search only hidden, non-installed updates
            $searchResult = $searcher.Search('IsInstalled=0 AND IsHidden=1')
            $foundKBs = @{}

            foreach ($update in $searchResult.Updates) {
                foreach ($kbId in $update.KBArticleIDs) {
                    if ($kbTargets -contains $kbId -and -not $foundKBs.ContainsKey($kbId)) {
                        $update.IsHidden = $false
                        $foundKBs[$kbId] = [string]$update.Title
                    }
                }
            }

            # Build results preserving input order
            foreach ($kb in $kbTargets) {
                if ($foundKBs.ContainsKey($kb)) {
                    [PSCustomObject]@{
                        KBArticle = "KB$kb"
                        Title     = $foundKBs[$kb]
                        Result    = 'Shown'
                    }
                } else {
                    [PSCustomObject]@{
                        KBArticle = "KB$kb"
                        Title     = $null
                        Result    = 'NotFound'
                    }
                }
            }
        }
    }

    process {
        foreach ($computer in $ComputerName) {
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Processing '$computer'"

            if (-not $PSCmdlet.ShouldProcess("$computer — KB$($normalizedKBIds -join ', KB')", 'Show Windows Update')) {
                continue
            }

            try {
                $invokeParams = @{
                    ComputerName = $computer
                    ScriptBlock  = $showScriptBlock
                    ArgumentList = @($kbCsv, [bool]$MicrosoftUpdate)
                }
                if ($PSBoundParameters.ContainsKey('Credential')) {
                    $invokeParams['Credential'] = $Credential
                }

                $results = Invoke-RemoteOrLocal @invokeParams

                foreach ($entry in $results) {
                    [PSCustomObject]@{
                        PSTypeName   = 'PSWinOps.WindowsUpdateShowResult'
                        ComputerName = $computer
                        Title        = $entry.Title
                        KBArticle    = $entry.KBArticle
                        Result       = $entry.Result
                        Timestamp    = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                    }
                }
            } catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${computer}': $_"
            }
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}