Site Overlay

Windows Server Installed Programs List & Software Inventory Report – PowerShell HTML Script

Windows Server Installed Programs List & Software Inventory Report – PowerShell HTML Script

Aşağıdaki komutu Powershell ile çalıştırmanız gerekmektedir.

# ==========================================
# Installed Software Audit Report (HTML)
# Read-only / Non-destructive
# ==========================================

$OutputDir  = "C:\Temp"
$OutputHtml = Join-Path $OutputDir "Installed_Software_Audit.html"

if (!(Test-Path $OutputDir)) {
    New-Item -ItemType Directory -Path $OutputDir | Out-Null
}

function Resolve-SIDToUser {
    param([System.Security.Principal.SecurityIdentifier]$Sid)

    if ($null -eq $Sid) { return $null }

    try {
        return $Sid.Translate([System.Security.Principal.NTAccount]).Value
    }
    catch {
        try { return $Sid.Value } catch { return $null }
    }
}

# Registry uninstall paths
$uninstallPaths = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)

# Installed software list
$programs = foreach ($path in $uninstallPaths) {
    Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object {
        $_.DisplayName -and $_.DisplayName.Trim() -ne ""
    } | Select-Object `
        @{Name='DisplayName';Expression={$_.DisplayName}},
        @{Name='DisplayVersion';Expression={$_.DisplayVersion}},
        @{Name='Publisher';Expression={$_.Publisher}},
        @{Name='InstallDateRaw';Expression={$_.InstallDate}},
        @{Name='InstallLocation';Expression={$_.InstallLocation}},
        @{Name='UninstallString';Expression={$_.UninstallString}},
        @{Name='RegistryPath';Expression={$_.PSPath}}
}

# Normalize InstallDate
$normalizedPrograms = foreach ($p in $programs) {
    $installDate = $null

    if ($p.InstallDateRaw -match '^\d{8}$') {
        try {
            $installDate = [datetime]::ParseExact($p.InstallDateRaw, 'yyyyMMdd', $null)
        }
        catch {
            $installDate = $null
        }
    }

    [PSCustomObject]@{
        DisplayName      = $p.DisplayName
        DisplayVersion   = $p.DisplayVersion
        Publisher        = $p.Publisher
        InstallDate      = $installDate
        InstallLocation  = $p.InstallLocation
        UninstallString  = $p.UninstallString
        RegistryPath     = $p.RegistryPath
    }
}

# MSI Installer events (if present in retained logs)
try {
    $msiEvents = Get-WinEvent -FilterHashtable @{
        LogName      = 'Application'
        ProviderName = 'MsiInstaller'
        Id           = 1033, 11707
    } -ErrorAction Stop | Select-Object TimeCreated, Id, ProviderName, Message, UserId
}
catch {
    $msiEvents = @()
}

# Correlate programs with MSI events
$result = foreach ($prog in ($normalizedPrograms | Sort-Object DisplayName, InstallDate -Unique)) {

    $matchedEvent = $null
    $matchedUser  = $null

    if ($prog.InstallDate) {
        $dayStart = $prog.InstallDate.Date
        $dayEnd   = $prog.InstallDate.Date.AddDays(1)

        $candidateEvents = $msiEvents | Where-Object {
            $_.TimeCreated -ge $dayStart -and $_.TimeCreated -lt $dayEnd -and
            $_.Message -like "*$($prog.DisplayName)*"
        }

        if (-not $candidateEvents -and $prog.DisplayName) {
            $firstWord = ($prog.DisplayName -split '\s+')[0]
            if ($firstWord) {
                $candidateEvents = $msiEvents | Where-Object {
                    $_.TimeCreated -ge $dayStart -and $_.TimeCreated -lt $dayEnd -and
                    $_.Message -like "*$firstWord*"
                }
            }
        }

        if (-not $candidateEvents -and $prog.Publisher) {
            $pubWord = ($prog.Publisher -split '\s+')[0]
            if ($pubWord) {
                $candidateEvents = $msiEvents | Where-Object {
                    $_.TimeCreated -ge $dayStart -and $_.TimeCreated -lt $dayEnd -and
                    $_.Message -like "*$pubWord*"
                }
            }
        }

        $matchedEvent = $candidateEvents | Sort-Object TimeCreated | Select-Object -First 1

        if ($matchedEvent) {
            $matchedUser = Resolve-SIDToUser -Sid $matchedEvent.UserId
        }
    }

    [PSCustomObject]@{
        ServerName      = $env:COMPUTERNAME
        DisplayName     = $prog.DisplayName
        DisplayVersion  = $prog.DisplayVersion
        Publisher       = $prog.Publisher
        InstallDate     = if ($prog.InstallDate) { $prog.InstallDate.ToString("yyyy-MM-dd") } else { "" }
        InstalledBy     = $matchedUser
        EventTime       = if ($matchedEvent) { $matchedEvent.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss") } else { "" }
        EventID         = if ($matchedEvent) { $matchedEvent.Id } else { "" }
        InstallLocation = $prog.InstallLocation
        UninstallString = $prog.UninstallString
        RegistryPath    = $prog.RegistryPath
    }
}

# Deduplicate final list by software/version/publisher/install date
$result = $result |
    Sort-Object DisplayName, DisplayVersion, Publisher, InstallDate -Unique

# Summary
$reportDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$totalCount = ($result | Measure-Object).Count
$withUser   = ($result | Where-Object { $_.InstalledBy -and $_.InstalledBy.Trim() -ne "" } | Measure-Object).Count

# HTML Style
$style = @"
<style>
body {
    font-family: Segoe UI, Arial, sans-serif;
    font-size: 12px;
    color: #222;
    margin: 20px;
}
h1, h2, h3 {
    color: #1f4e79;
}
table {
    border-collapse: collapse;
    width: 100%;
    table-layout: auto;
}
th, td {
    border: 1px solid #d9d9d9;
    padding: 6px 8px;
    vertical-align: top;
    text-align: left;
    word-wrap: break-word;
}
th {
    background-color: #1f4e79;
    color: white;
    position: sticky;
    top: 0;
}
tr:nth-child(even) {
    background-color: #f7f9fc;
}
.summary {
    margin-bottom: 20px;
    padding: 12px;
    background: #eef4fb;
    border: 1px solid #c7d7ea;
}
.note {
    margin-top: 15px;
    padding: 10px;
    background: #fff8e1;
    border: 1px solid #f0d58c;
}
.small {
    font-size: 11px;
    color: #555;
}
</style>
"@

# HTML Header / Summary
$preContent = @"
<h1>Installed Software Audit Report</h1>
<div class='summary'>
    <b>Server Name:</b> $env:COMPUTERNAME<br>
    <b>Report Date:</b> $reportDate<br>
    <b>Total Software Count:</b> $totalCount<br>
    <b>Entries with InstalledBy Detected:</b> $withUser
</div>

<div class='note'>
    <b>Note:</b> InstalledBy field may be blank for non-MSI installations, software deployed by SCCM/Intune/GPO/service accounts,
    or when relevant event logs are no longer retained on the server.
</div>
<br>
"@

# Generate HTML
$result |
    Sort-Object InstallDate, DisplayName |
    ConvertTo-Html -Head $style -PreContent $preContent -Title "Installed Software Audit Report" |
    Out-File -FilePath $OutputHtml -Encoding UTF8

Write-Host "HTML rapor oluşturuldu: $OutputHtml" -ForegroundColor Green
arif akyuz
Arif Akyüz

image
Başka cihazda görüntüle
Arif Akyüz Sistem Network Yöneticisi ve Siber Güvenlik Uzmanı
Arif Akyüz Sistem Network Yöneticisi ve Siber Güvenlik Uzmanı

Arif Akyüz
Bilgi Teknolojileri
Sistem Network Yöneticisi
ve Siber Güvenlik Uzmanı
[email protected]

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors