-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add environment report export #4928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| function Get-WinUtilEnvironmentReport { | ||
| <# | ||
| .SYNOPSIS | ||
| Collects the allowlisted data used by the WinUtil environment report. | ||
| #> | ||
|
|
||
| $windows = [ordered]@{ | ||
| edition = $null | ||
| version = $null | ||
| buildNumber = $null | ||
| architecture = $null | ||
| } | ||
| $hardware = [ordered]@{ | ||
| cpuModel = $null | ||
| logicalProcessorCount = $null | ||
| totalMemoryGB = $null | ||
| } | ||
|
|
||
| try { | ||
| $operatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop | ||
| $windows.edition = $operatingSystem.Caption | ||
| $windows.version = $operatingSystem.Version | ||
| $windows.buildNumber = $operatingSystem.BuildNumber | ||
| $windows.architecture = $operatingSystem.OSArchitecture | ||
|
|
||
| if ($null -ne $operatingSystem.TotalVisibleMemorySize) { | ||
| $hardware.totalMemoryGB = [math]::Round(([double]$operatingSystem.TotalVisibleMemorySize / 1MB), 2) | ||
| } | ||
| } catch { | ||
| } | ||
|
|
||
| try { | ||
| $processors = @(Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop) | ||
| if ($processors.Count -gt 0) { | ||
| $hardware.cpuModel = $processors[0].Name | ||
| $hardware.logicalProcessorCount = [int](($processors | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum) | ||
| } | ||
| } catch { | ||
| } | ||
|
|
||
| $developerTools = [ordered]@{} | ||
| $toolDefinitions = [ordered]@{ | ||
| git = @("git", "--version") | ||
| java = @("java", "-version") | ||
| nodejs = @("node", "--version") | ||
| python = @("python", "--version") | ||
| docker = @("docker", "--version") | ||
| } | ||
|
|
||
| foreach ($toolName in $toolDefinitions.Keys) { | ||
| $toolReport = [ordered]@{ | ||
| installed = $false | ||
| version = $null | ||
| } | ||
|
|
||
| try { | ||
| $command = Get-Command -Name $toolDefinitions[$toolName][0] -CommandType Application -ErrorAction Stop | | ||
| Select-Object -First 1 | ||
| if ($null -ne $command) { | ||
| $toolReport.installed = $true | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On stock Windows systems where the Microsoft Store Python execution alias is enabled but Python is not actually installed, Useful? React with 👍 / 👎. |
||
| $output = @(& $command.Source $toolDefinitions[$toolName][1] 2>&1 | ForEach-Object { $_.ToString().Trim() }) -join "`n" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Add a timeout for each developer-tool probe. At Line 61, the native command waits without a timeout. Start each process with a finite timeout. If it expires, terminate the process and leave its version unavailable. 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
WinUtil relaunches itself elevated before the UI starts, so this diagnostic button runs whichever Useful? React with 👍 / 👎. |
||
| $versionMatch = [regex]::Match($output, '\d+(?:\.\d+)+(?:[-+._A-Za-z0-9]+)?') | ||
| if ($versionMatch.Success) { | ||
| $toolReport.version = $versionMatch.Value | ||
| } | ||
| } | ||
| } catch { | ||
| } | ||
|
|
||
| $developerTools[$toolName] = [pscustomobject]$toolReport | ||
| } | ||
|
|
||
| $windowsFeatures = [ordered]@{} | ||
| $featureDefinitions = [ordered]@{ | ||
| hyperV = "Microsoft-Hyper-V-All" | ||
| wsl = "Microsoft-Windows-Subsystem-Linux" | ||
| windowsSandbox = "Containers-DisposableClientVM" | ||
| } | ||
|
|
||
| foreach ($featureName in $featureDefinitions.Keys) { | ||
| $featureReport = [ordered]@{ enabled = $false } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| try { | ||
| $feature = Get-WindowsOptionalFeature -Online -FeatureName $featureDefinitions[$featureName] -ErrorAction Stop | ||
| $featureReport.enabled = $feature.State -eq "Enabled" | ||
| } catch { | ||
| } | ||
|
|
||
| $windowsFeatures[$featureName] = [pscustomobject]$featureReport | ||
| } | ||
|
|
||
| return [pscustomobject][ordered]@{ | ||
| schemaVersion = "1.0" | ||
| generatedAtUtc = [DateTime]::UtcNow.ToString("o") | ||
| windows = [pscustomobject]$windows | ||
| hardware = [pscustomobject]$hardware | ||
| powershell = [pscustomobject][ordered]@{ | ||
| edition = $PSVersionTable.PSEdition | ||
| version = $PSVersionTable.PSVersion.ToString() | ||
| } | ||
| developerTools = [pscustomobject]$developerTools | ||
| windowsFeatures = [pscustomobject]$windowsFeatures | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| function Invoke-WPFExportEnvironmentReport { | ||
| <# | ||
| .SYNOPSIS | ||
| Exports an allowlisted, read-only environment report as JSON. | ||
| #> | ||
|
|
||
| try { | ||
| Add-Type -AssemblyName System.Windows.Forms | ||
| $dialog = [System.Windows.Forms.SaveFileDialog]::new() | ||
| $dialog.Title = "Export Environment Report" | ||
| $dialog.Filter = "JSON files (*.json)|*.json" | ||
| $dialog.FileName = "WinUtilEnvironmentReport_$(Get-Date -Format 'yyyyMMdd').json" | ||
| $dialog.InitialDirectory = [Environment]::GetFolderPath("Desktop") | ||
|
|
||
| if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { | ||
| return | ||
| } | ||
|
|
||
| $report = Get-WinUtilEnvironmentReport | ||
| $json = $report | ConvertTo-Json -Depth 6 | ||
| [System.IO.File]::WriteAllText($dialog.FileName, $json, [System.Text.UTF8Encoding]::new($false)) | ||
|
|
||
| Write-WinUtilLog -Component "EnvironmentReport" -Message "Environment report exported." | ||
| [System.Windows.MessageBox]::Show( | ||
| "The environment report was exported successfully.", | ||
| "Environment Report", | ||
| "OK", | ||
| "Information" | ||
| ) | Out-Null | ||
| } catch { | ||
| Write-WinUtilLog -Component "EnvironmentReport" -Level "ERROR" -Message "Environment report export failed: $($_.Exception.Message)" | ||
| [System.Windows.MessageBox]::Show( | ||
| "The environment report could not be exported. $($_.Exception.Message)", | ||
| "Environment Report", | ||
| "OK", | ||
| "Error" | ||
| ) | Out-Null | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #=========================================================================== | ||
| # Tests - Environment Report | ||
| #=========================================================================== | ||
|
|
||
| BeforeAll { | ||
| . (Join-Path (Resolve-Path (Join-Path $PSScriptRoot "..")) "functions\private\Get-WinUtilEnvironmentReport.ps1") | ||
| } | ||
|
|
||
| Describe "Get-WinUtilEnvironmentReport" { | ||
| BeforeEach { | ||
| Mock Get-Command { $null } -ParameterFilter { $CommandType -eq "Application" } | ||
| Mock Get-CimInstance { | ||
| switch ($ClassName) { | ||
| "Win32_OperatingSystem" { | ||
| return [pscustomobject]@{ | ||
| Caption = "Windows 11 Pro" | ||
| Version = "10.0.26100" | ||
| BuildNumber = "26100" | ||
| OSArchitecture = "64-bit" | ||
| TotalVisibleMemorySize = 16777216 | ||
| } | ||
| } | ||
| "Win32_Processor" { | ||
| return [pscustomobject]@{ | ||
| Name = "Example CPU" | ||
| NumberOfLogicalProcessors = 8 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Mock Get-WindowsOptionalFeature { | ||
| return [pscustomobject]@{ State = "Enabled" } | ||
| } | ||
| } | ||
|
|
||
| It "returns the versioned allowlisted report schema" { | ||
| $report = Get-WinUtilEnvironmentReport | ||
|
|
||
| $report.schemaVersion | Should -Be "1.0" | ||
| $report.generatedAtUtc | Should -Match '^\d{4}-\d{2}-\d{2}T' | ||
| $report.windows.edition | Should -Be "Windows 11 Pro" | ||
| $report.hardware.cpuModel | Should -Be "Example CPU" | ||
| $report.hardware.logicalProcessorCount | Should -Be 8 | ||
| $report.hardware.totalMemoryGB | Should -Be 16 | ||
| $report.powershell.PSObject.Properties.Name | Should -Be @("edition", "version") | ||
| $report.developerTools.PSObject.Properties.Name | Should -Be @("git", "java", "nodejs", "python", "docker") | ||
| $report.windowsFeatures.PSObject.Properties.Name | Should -Be @("hyperV", "wsl", "windowsSandbox") | ||
| $report.windowsFeatures.hyperV.enabled | Should -BeTrue | ||
| } | ||
|
|
||
| It "contains no fields outside the approved report schema" { | ||
| $report = Get-WinUtilEnvironmentReport | ConvertTo-Json -Depth 6 | ||
|
|
||
| $report | Should -Not -Match 'ComputerName|UserName|UserProfile|IPAddress|MacAddress|SerialNumber|ProductKey|Environment' | ||
| } | ||
|
|
||
| It "keeps missing developer tools and unavailable features non-terminating" { | ||
| Mock Get-WindowsOptionalFeature { throw "Feature unavailable" } | ||
|
|
||
| $report = Get-WinUtilEnvironmentReport | ||
|
|
||
| $report.developerTools.git.installed | Should -BeFalse | ||
| $report.developerTools.git.version | Should -BeNullOrEmpty | ||
| $report.windowsFeatures.hyperV.enabled | Should -BeFalse | ||
| $report.windowsFeatures.wsl.enabled | Should -BeFalse | ||
| $report.windowsFeatures.windowsSandbox.enabled | Should -BeFalse | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Document the report metadata fields.
Get-WinUtilEnvironmentReportalso emits top-levelschemaVersionandgeneratedAtUtc. Because this sentence says the report contains “only” the listed fields, add both fields to keep the documentation consistent with the exported JSON.🤖 Prompt for AI Agents