Site Overlay

Windows Server Yüklü Programlar Listesi ve Yazılım Envanter Raporu – PowerShell Script

Windows Server Yüklü Programlar Listesi ve Yazılım Envanter Raporu – PowerShell Script

PowerShell ile çalıştırın.

# ==========================================
# Installed Software Audit Report (HTML)
# Windows Server 2019 / 2022
# 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

Author: Arif Akyüz

Arif Akyüz - Siber Güvenlik Uzmanı ve Dijital Dönüşüm Lideri 15 yılı aşkın süredir bilişim sektöründe, özellikle siber güvenlik ve dijital dönüşüm alanlarında hizmet veren bir uzmanım. Hem stratejik hem de teknik düzeyde çözümler sunarak organizasyonların güvenlik altyapılarını güçlendiriyor ve dijitalleşme süreçlerini yönetiyorum.