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