Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing [void][System.Windows.Forms.Application]::EnableVisualStyles() [void][System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false) $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { $res = [System.Windows.Forms.MessageBox]::Show( "This script needs Administrator rights to export drivers.`n`nRestart it with elevated permissions?", "Driver Backup GUI", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question ) if ($res -eq [System.Windows.Forms.DialogResult]::Yes) { try { $scriptPath = $PSCommandPath if (-not $scriptPath) { $scriptPath = $MyInvocation.MyCommand.Path } $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = "powershell.exe" $psi.Arguments = "-ExecutionPolicy Bypass -File `"$scriptPath`"" $psi.Verb = "runas" [System.Diagnostics.Process]::Start($psi) | Out-Null } catch { [System.Windows.Forms.MessageBox]::Show( "Could not restart script elevated:`n$($_.Exception.Message)", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) } } return } Add-Type @" using System; using System.Runtime.InteropServices; public static class NativeMethods { [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); } "@ $hWnd = [NativeMethods]::GetConsoleWindow() if ($hWnd -ne [IntPtr]::Zero) { [NativeMethods]::ShowWindow($hWnd, 0) } $Theme = @{ Bg = [System.Drawing.Color]::FromArgb(245,247,250) CardBg = [System.Drawing.Color]::White Border = [System.Drawing.Color]::FromArgb(210,218,228) Text = [System.Drawing.Color]::FromArgb(15,23,42) Muted = [System.Drawing.Color]::FromArgb(100,116,139) Accent = [System.Drawing.Color]::FromArgb(0,120,212) AccentHover= [System.Drawing.Color]::FromArgb(0,102,180) HeaderBack = [System.Drawing.Color]::FromArgb(0, 51, 102) TabBg = [System.Drawing.Color]::FromArgb(235,238,244) TabHover = [System.Drawing.Color]::FromArgb(225,230,238) GridAlt = [System.Drawing.Color]::FromArgb(247,250,255) SelectBg = [System.Drawing.Color]::FromArgb(200,230,255) } $Theme.Font = New-Object System.Drawing.Font("Segoe UI", 9) $Theme.FontBold = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) $Theme.FontHint = New-Object System.Drawing.Font("Segoe UI", 8, ([System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Italic)) function Apply-ModernButton { param( [Parameter(Mandatory=$true)][System.Windows.Forms.Button]$Button, [ValidateSet('Primary','Secondary')][string]$Variant = 'Primary' ) $Button.UseVisualStyleBackColor = $false $Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat $Button.FlatAppearance.BorderSize = 1 $Button.Cursor = [System.Windows.Forms.Cursors]::Hand $Button.Font = $Theme.FontBold if ($Variant -eq 'Primary') { $Button.BackColor = $Theme.Accent $Button.ForeColor = [System.Drawing.Color]::White $Button.FlatAppearance.BorderColor = $Theme.Accent $Button.Add_MouseEnter({ $this.BackColor = $Theme.AccentHover }) $Button.Add_MouseLeave({ $this.BackColor = $Theme.Accent }) } else { $Button.BackColor = $Theme.TabBg $Button.ForeColor = $Theme.Text $Button.FlatAppearance.BorderColor = $Theme.Border $Button.Add_MouseEnter({ $this.BackColor = $Theme.TabHover }) $Button.Add_MouseLeave({ $this.BackColor = $Theme.TabBg }) } } function Style-ProgressForm { param( [Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Form ) $Form.BackColor = $Theme.CardBg $Form.Font = $Theme.Font } $ColumnWidths = @{ Check = 24 InfName = 70 DeviceName = 220 ProviderName = 90 Manufacturer = 90 DriverVersion= 100 DriverDate = 65 DeviceClass = 120 DeviceID = 300 Status = 90 } function Sanitize-Name { param([string]$Name) if (-not $Name) { return "Unknown" } $invalid = [System.IO.Path]::GetInvalidFileNameChars() + [System.IO.Path]::GetInvalidPathChars() $clean = $Name foreach ($c in $invalid) { $pattern = [regex]::Escape([string]$c) $clean = $clean -replace $pattern, "" } $clean = $clean.Trim() $maxLen = 80 if ($clean.Length -gt $maxLen) { $clean = $clean.Substring(0, $maxLen) } if ([string]::IsNullOrWhiteSpace($clean)) { $clean = "Unknown" } return $clean } function Get-CategoryName { param([string]$DeviceClass) if (-not $DeviceClass) { return "Other" } $dc = $DeviceClass.ToUpperInvariant() switch ($dc) { 'NET' { 'Network' } 'MEDIA' { 'Sound' } 'DISPLAY' { 'Display' } 'HIDCLASS' { 'Input' } 'USB' { 'USB' } 'PORTS' { 'Ports' } 'SYSTEM' { 'System' } 'HDC' { 'StorageController' } 'SCSIADAPTER' { 'StorageController' } default { Sanitize-Name $DeviceClass } } } function Get-DriverList { try { $raw = Get-WmiObject Win32_PnPSignedDriver -ErrorAction Stop } catch { [System.Windows.Forms.MessageBox]::Show( "Error reading driver list from WMI:`n$($_.Exception.Message)", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) return @() } foreach ($d in $raw) { if ($d.DeviceID -eq 'HTREE\ROOT\0' -or [string]::IsNullOrWhiteSpace($d.InfName)) { continue } $date = $null if ($d.DriverDate) { try { $date = [System.Management.ManagementDateTimeConverter]::ToDateTime($d.DriverDate) } catch {} } [PSCustomObject]@{ InfName = $d.InfName DeviceName = $d.DeviceName ProviderName = $d.DriverProviderName Manufacturer = $d.Manufacturer DriverVersion = $d.DriverVersion DriverDate = $date DeviceClass = $d.DeviceClass DeviceID = $d.DeviceID Status = "Present" } } } $allDrivers = Get-DriverList $thirdPartyDrivers = $allDrivers | Where-Object { $_.ProviderName -notlike "*Microsoft*" } $microsoftDrivers = $allDrivers | Where-Object { $_.ProviderName -like "*Microsoft*" } $driverByInf = @{} foreach ($d in $allDrivers) { if ([string]::IsNullOrWhiteSpace($d.InfName)) { continue } if (-not $driverByInf.ContainsKey($d.InfName)) { $driverByInf[$d.InfName] = $d } } $script:DriverSourceMode = 'Current' $script:OfflineWindowsRoot = $null $script:SortStates = @{} function Handle-HeaderClick { param( [System.Windows.Forms.DataGridView]$Grid, [System.Windows.Forms.DataGridViewCellMouseEventArgs]$EventArgs ) $colIndex = $EventArgs.ColumnIndex if ($colIndex -eq 0) { return } $col = $Grid.Columns[$colIndex] $gridKey = if ($Grid.Name) { $Grid.Name } else { "Grid" } $colKey = if ($col.Name) { $col.Name } else { "Col$colIndex" } $stateKey = "${gridKey}:${colKey}" $prev = $script:SortStates[$stateKey] if (-not $prev -or $prev -eq 'Desc') { $newState = 'Asc' $dir = [System.ComponentModel.ListSortDirection]::Ascending $glyph = [System.Windows.Forms.SortOrder]::Ascending } else { $newState = 'Desc' $dir = [System.ComponentModel.ListSortDirection]::Descending $glyph = [System.Windows.Forms.SortOrder]::Descending } $script:SortStates[$stateKey] = $newState foreach ($c in $Grid.Columns) { if ($c.Index -ne 0) { $c.HeaderCell.SortGlyphDirection = [System.Windows.Forms.SortOrder]::None } } $Grid.Sort($col, $dir) $col.HeaderCell.SortGlyphDirection = $glyph } function New-DriverGrid { $grid = New-Object System.Windows.Forms.DataGridView $grid.AllowUserToAddRows = $false $grid.AllowUserToDeleteRows = $false $grid.MultiSelect = $true $grid.SelectionMode = 'CellSelect' $grid.ReadOnly = $false $grid.EditMode = 'EditOnEnter' $grid.ClipboardCopyMode = [System.Windows.Forms.DataGridViewClipboardCopyMode]::EnableWithoutHeaderText $grid.AutoSizeColumnsMode = 'None' $grid.ColumnHeadersHeightSizeMode = 'AutoSize' $grid.Anchor = 'Top,Left,Right,Bottom' $grid.RowHeadersVisible = $false $grid.ScrollBars = 'Both' $grid.BorderStyle = 'None' $grid.GridColor = $Theme.Border $grid.BackgroundColor = $Theme.CardBg $grid.CellBorderStyle = [System.Windows.Forms.DataGridViewCellBorderStyle]::SingleHorizontal $grid.ColumnHeadersBorderStyle = [System.Windows.Forms.DataGridViewHeaderBorderStyle]::None $headerStyle = New-Object System.Windows.Forms.DataGridViewCellStyle $headerStyle.BackColor = $Theme.HeaderBack $headerStyle.ForeColor = [System.Drawing.Color]::White $headerStyle.Font = New-Object System.Drawing.Font("Segoe UI", 9,[System.Drawing.FontStyle]::Bold) $headerStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft $grid.ColumnHeadersDefaultCellStyle = $headerStyle $grid.EnableHeadersVisualStyles = $false $rowStyle = New-Object System.Windows.Forms.DataGridViewCellStyle $rowStyle.Font = New-Object System.Drawing.Font("Segoe UI", 9) $rowStyle.SelectionBackColor = $Theme.SelectBg $rowStyle.SelectionForeColor = [System.Drawing.Color]::Black $grid.DefaultCellStyle = $rowStyle $altStyle = New-Object System.Windows.Forms.DataGridViewCellStyle $altStyle.BackColor = $Theme.GridAlt $grid.AlternatingRowsDefaultCellStyle = $altStyle $colCheck = New-Object System.Windows.Forms.DataGridViewCheckBoxColumn $colCheck.Name = "Check" $colCheck.HeaderText = "" $colCheck.TrueValue = $true $colCheck.FalseValue = $false $colCheck.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable $colCheck.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colCheck.Width = $ColumnWidths.Check $checkStyle = New-Object System.Windows.Forms.DataGridViewCellStyle $checkStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $colCheck.DefaultCellStyle = $checkStyle $grid.Columns.Add($colCheck) | Out-Null $colInf = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colInf.Name = "InfName" $colInf.HeaderText = "INF Name" $colInf.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colInf.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colInf.Width = $ColumnWidths.InfName $grid.Columns.Add($colInf) | Out-Null $colDev = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colDev.Name = "DeviceName" $colDev.HeaderText = "Device" $colDev.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colDev.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colDev.Width = $ColumnWidths.DeviceName $grid.Columns.Add($colDev) | Out-Null $colProv = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colProv.Name = "ProviderName" $colProv.HeaderText = "Provider" $colProv.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colProv.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colProv.Width = $ColumnWidths.ProviderName $grid.Columns.Add($colProv) | Out-Null $colMan = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colMan.Name = "Manufacturer" $colMan.HeaderText = "Manufacturer" $colMan.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colMan.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colMan.Width = $ColumnWidths.Manufacturer $grid.Columns.Add($colMan) | Out-Null $colVer = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colVer.Name = "DriverVersion" $colVer.HeaderText = "Version" $colVer.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colVer.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colVer.Width = $ColumnWidths.DriverVersion $grid.Columns.Add($colVer) | Out-Null $colDate = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colDate.Name = "DriverDate" $colDate.HeaderText = "Date" $colDate.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colDate.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colDate.Width = $ColumnWidths.DriverDate $grid.Columns.Add($colDate) | Out-Null $colClass = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colClass.Name = "DeviceClass" $colClass.HeaderText = "Class" $colClass.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colClass.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colClass.Width = $ColumnWidths.DeviceClass $grid.Columns.Add($colClass) | Out-Null $colId = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colId.Name = "DeviceID" $colId.HeaderText = "Device ID" $colId.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colId.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colId.Width = $ColumnWidths.DeviceID $grid.Columns.Add($colId) | Out-Null $colStatus = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colStatus.Name = "Status" $colStatus.HeaderText = "Status" $colStatus.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::Programmatic $colStatus.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::None $colStatus.Width = $ColumnWidths.Status $grid.Columns.Add($colStatus) | Out-Null $grid.Add_EditingControlShowing({ param($sender, $e) $tb = $e.Control -as [System.Windows.Forms.DataGridViewTextBoxEditingControl] if ($tb -ne $null) { $tb.ReadOnly = $true } }) $cms = New-Object System.Windows.Forms.ContextMenuStrip $cms.Tag = $grid $miCopy = $cms.Items.Add("Copy") $miCopy.Add_Click({ param($sender, $e) $cmsLocal = $sender.GetCurrentParent() $g = [System.Windows.Forms.DataGridView]$cmsLocal.Tag $tb = $g.EditingControl -as [System.Windows.Forms.DataGridViewTextBoxEditingControl] if ($tb -and $tb.SelectionLength -gt 0) { [System.Windows.Forms.Clipboard]::SetText($tb.SelectedText) return } $selectedCount = $g.GetCellCount([System.Windows.Forms.DataGridViewElementStates]::Selected) if ($selectedCount -gt 0) { $sb = New-Object System.Text.StringBuilder $rows = $g.SelectedCells | Sort-Object RowIndex, ColumnIndex | Group-Object RowIndex foreach ($rg in $rows) { $first = $true foreach ($cell in $rg.Group | Sort-Object ColumnIndex) { if (-not $first) { [void]$sb.Append("`t") } [void]$sb.Append([string]$cell.Value) $first = $false } [void]$sb.AppendLine() } [System.Windows.Forms.Clipboard]::SetText($sb.ToString()) } }) $grid.ContextMenuStrip = $cms $grid.Add_MouseDown({ param($sender, $e) if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Right) { $hit = $sender.HitTest($e.X, $e.Y) if ($hit.RowIndex -ge 0 -and $hit.ColumnIndex -ge 0) { $sender.ClearSelection() $sender.CurrentCell = $sender.Rows[$hit.RowIndex].Cells[$hit.ColumnIndex] $sender.Rows[$hit.RowIndex].Cells[$hit.ColumnIndex].Selected = $true } } }) return $grid } function Add-SelectAllCheckbox { param( [System.Windows.Forms.TabPage]$Tab, [System.Windows.Forms.DataGridView]$Grid, [bool]$DefaultChecked ) $cb = New-Object System.Windows.Forms.CheckBox $cb.Text = "Select / Deselect All" $cb.AutoSize = $true $cb.Location = New-Object System.Drawing.Point(10, 5) $cb.Anchor = 'Top,Left' $cb.Checked = $DefaultChecked $cb.Tag = $Grid $cb.ForeColor = $Theme.Text $cb.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat $lblHint = New-Object System.Windows.Forms.Label $lblHint.Text = "Tip: Click column headers to sort ascending/descending." $lblHint.AutoSize = $true $lblHint.Location = New-Object System.Drawing.Point(170, 7) $lblHint.Anchor = 'Top,Left' $lblHint.ForeColor = $Theme.Muted $fs = [System.Drawing.FontStyle]( [System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Italic ) $lblHint.Font = $Theme.FontHint $cb.Add_CheckedChanged({ param($sender, $e) $gridRef = [System.Windows.Forms.DataGridView]$sender.Tag if ($null -eq $gridRef) { return } $check = $sender.Checked foreach ($row in $gridRef.Rows) { $row.Cells[0].Value = $check } }) $Grid.Location = New-Object System.Drawing.Point(0, 30) $Grid.Anchor = 'Top,Left,Right,Bottom' $w = [int]$Tab.ClientSize.Width $h = [int]$Tab.ClientSize.Height - 35 if ($h -lt 50) { $h = 50 } $Grid.Size = New-Object System.Drawing.Size($w, $h) $Tab.Controls.Add($cb) $Tab.Controls.Add($lblHint) $Tab.Controls.Add($Grid) } $form = New-Object System.Windows.Forms.Form $form.Text = "Driver Backup GUI" $form.Size = New-Object System.Drawing.Size(1100, 600) $form.StartPosition = 'CenterScreen' $form.MinimumSize = New-Object System.Drawing.Size(900, 500) $form.Font = $Theme.Font $form.BackColor = $Theme.Bg $form.DoubleBuffered = $true $tabControl = New-Object System.Windows.Forms.TabControl $tabControl.BackColor = $Theme.Bg $tabControl.Location = New-Object System.Drawing.Point(10, 60) $tabControl.Size = New-Object System.Drawing.Size(1060, 450) $tabControl.Anchor = 'Top,Left,Right,Bottom' $tabControl.DrawMode = [System.Windows.Forms.TabDrawMode]::Normal $tabControl.SizeMode = [System.Windows.Forms.TabSizeMode]::Normal $grpSource = New-Object System.Windows.Forms.GroupBox $grpSource.Text = "Driver source" $grpSource.Location = New-Object System.Drawing.Point(10, 8) $grpSource.Size = New-Object System.Drawing.Size(1060, 46) $grpSource.Anchor = 'Top,Left,Right' $grpSource.BackColor = $Theme.Bg $grpSource.ForeColor = $Theme.Text $grpSource.Font = $Theme.Font $rbCurrentWindows = New-Object System.Windows.Forms.RadioButton $rbCurrentWindows.Text = "Running Windows" $rbCurrentWindows.AutoSize = $true $rbCurrentWindows.Checked = $true $rbCurrentWindows.Location = New-Object System.Drawing.Point(12, 18) $rbCurrentWindows.ForeColor = $Theme.Text $rbCurrentWindows.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat $rbOfflineWindows = New-Object System.Windows.Forms.RadioButton $rbOfflineWindows.Text = "Offline Windows folder" $rbOfflineWindows.AutoSize = $true $rbOfflineWindows.Location = New-Object System.Drawing.Point(145, 18) $rbOfflineWindows.ForeColor = $Theme.Text $rbOfflineWindows.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat $txtOfflineWindows = New-Object System.Windows.Forms.TextBox $txtOfflineWindows.Location = New-Object System.Drawing.Point(305, 16) $txtOfflineWindows.Size = New-Object System.Drawing.Size(390, 22) $txtOfflineWindows.Anchor = 'Top,Left,Right' $txtOfflineWindows.Enabled = $false $txtOfflineWindows.Font = $Theme.Font $btnBrowseOfflineWindows = New-Object System.Windows.Forms.Button $btnBrowseOfflineWindows.Text = "Browse..." $btnBrowseOfflineWindows.Size = New-Object System.Drawing.Size(85, 24) $btnBrowseOfflineWindows.Location = New-Object System.Drawing.Point(705, 14) $btnBrowseOfflineWindows.Anchor = 'Top,Right' $btnBrowseOfflineWindows.Enabled = $false $btnBrowseOfflineWindows.Font = New-Object System.Drawing.Font("Segoe UI", 8,[System.Drawing.FontStyle]::Bold) Apply-ModernButton -Button $btnBrowseOfflineWindows -Variant 'Secondary' $btnReloadSource = New-Object System.Windows.Forms.Button $btnReloadSource.Text = "Reload" $btnReloadSource.Size = New-Object System.Drawing.Size(75, 24) $btnReloadSource.Location = New-Object System.Drawing.Point(798, 14) $btnReloadSource.Anchor = 'Top,Right' $btnReloadSource.Font = New-Object System.Drawing.Font("Segoe UI", 8,[System.Drawing.FontStyle]::Bold) Apply-ModernButton -Button $btnReloadSource -Variant 'Secondary' $lblSourceStatus = New-Object System.Windows.Forms.Label $lblSourceStatus.Text = "Source: running Windows" $lblSourceStatus.AutoSize = $false $lblSourceStatus.Location = New-Object System.Drawing.Point(882, 18) $lblSourceStatus.Size = New-Object System.Drawing.Size(165, 18) $lblSourceStatus.Anchor = 'Top,Right' $lblSourceStatus.ForeColor = $Theme.Muted $lblSourceStatus.Font = $Theme.Font $grpSource.Controls.Add($rbCurrentWindows) $grpSource.Controls.Add($rbOfflineWindows) $grpSource.Controls.Add($txtOfflineWindows) $grpSource.Controls.Add($btnBrowseOfflineWindows) $grpSource.Controls.Add($btnReloadSource) $grpSource.Controls.Add($lblSourceStatus) $form.Controls.Add($grpSource) $tabThird = New-Object System.Windows.Forms.TabPage $tabThird.Text = "Third-Party Drivers" $tabThird.BackColor = $Theme.CardBg $tabMs = New-Object System.Windows.Forms.TabPage $tabMs.Text = "Microsoft Drivers" $tabMs.BackColor = $Theme.CardBg $tabRestore = New-Object System.Windows.Forms.TabPage $tabRestore.Text = "Restore Drivers" $tabRestore.BackColor = $Theme.CardBg $gridThird = New-DriverGrid $gridThird.Name = "ThirdGrid" $gridMs = New-DriverGrid $gridMs.Name = "MsGrid" $gridRestore = New-DriverGrid $gridRestore.Name = "RestoreGrid" [void]$tabControl.TabPages.Add($tabThird) [void]$tabControl.TabPages.Add($tabMs) [void]$tabControl.TabPages.Add($tabRestore) $form.Controls.Add($tabControl) Add-SelectAllCheckbox -Tab $tabThird -Grid $gridThird -DefaultChecked $true Add-SelectAllCheckbox -Tab $tabMs -Grid $gridMs -DefaultChecked $false Add-SelectAllCheckbox -Tab $tabRestore -Grid $gridRestore -DefaultChecked $false $chkShowNotPresent = New-Object System.Windows.Forms.CheckBox $chkShowNotPresent.Text = "Show drivers for devices not currently present" $chkShowNotPresent.AutoSize = $false $chkShowNotPresent.Size = [System.Drawing.Size]::new(340, 22) $chkShowNotPresent.ForeColor = $Theme.Text $chkShowNotPresent.BackColor = $Theme.CardBg $chkShowNotPresent.Font = $Theme.Font $chkShowNotPresent.FlatStyle = [System.Windows.Forms.FlatStyle]::Standard $chkShowNotPresent.Checked = $false $chkShowNotPresent.Anchor = 'Top,Right' $tabThird.Controls.Add($chkShowNotPresent) function Update-ThirdPartyTopControlsLayout { $x = [Math]::Max(500, ([int]$tabThird.ClientSize.Width - [int]$chkShowNotPresent.Width - 10)) $chkShowNotPresent.Location = [System.Drawing.Point]::new($x, 3) $chkShowNotPresent.BringToFront() } Update-ThirdPartyTopControlsLayout $tabThird.Add_Resize({ Update-ThirdPartyTopControlsLayout }) $script:RestoreRoot = $null $btnSelectRestore = New-Object System.Windows.Forms.Button $btnSelectRestore.Text = "Select restore folder" $btnSelectRestore.Size = New-Object System.Drawing.Size(160, 24) $btnSelectRestore.Location = New-Object System.Drawing.Point(690, 3) $btnSelectRestore.Anchor = 'Top,Right' $btnSelectRestore.Font = New-Object System.Drawing.Font("Segoe UI", 8,[System.Drawing.FontStyle]::Bold) Apply-ModernButton -Button $btnSelectRestore -Variant 'Secondary' $btnInstall = New-Object System.Windows.Forms.Button $btnInstall.Text = "Install selected" $btnInstall.Size = New-Object System.Drawing.Size(150, 24) $btnInstall.Location = New-Object System.Drawing.Point(860, 3) $btnInstall.Anchor = 'Top,Right' $btnInstall.Font = New-Object System.Drawing.Font("Segoe UI", 8,[System.Drawing.FontStyle]::Bold) Apply-ModernButton -Button $btnInstall -Variant 'Primary' $tabRestore.Controls.Add($btnSelectRestore) $tabRestore.Controls.Add($btnInstall) function Update-RestoreButtonsLayout { param([System.Windows.Forms.TabPage]$Tab) if (-not $Tab) { return } $margin = 10 $btnInstall.Left = [Math]::Max($margin, $Tab.ClientSize.Width - $btnInstall.Width - $margin) $btnSelectRestore.Left = [Math]::Max($margin, $btnInstall.Left - $btnSelectRestore.Width - $margin) $btnInstall.Top = 3 $btnSelectRestore.Top = 3 $btnInstall.BringToFront() $btnSelectRestore.BringToFront() } Update-RestoreButtonsLayout -Tab $tabRestore $tabRestore.Add_Resize({ Update-RestoreButtonsLayout -Tab $tabRestore }) $tabControl.Add_SelectedIndexChanged({ if ($tabControl.SelectedTab -eq $tabRestore) { Update-RestoreButtonsLayout -Tab $tabRestore } if ($tabControl.SelectedTab -eq $tabThird) { Update-ThirdPartyTopControlsLayout } }) $btnBackup = New-Object System.Windows.Forms.Button $btnBackup.Text = "Backup selected drivers" $btnBackup.Size = New-Object System.Drawing.Size(220, 32) $btnBackup.Location = New-Object System.Drawing.Point(10, 520) $btnBackup.Anchor = 'Left,Bottom' $btnBackup.Font = New-Object System.Drawing.Font("Segoe UI", 9,[System.Drawing.FontStyle]::Bold) Apply-ModernButton -Button $btnBackup -Variant 'Primary' $lblInfo = New-Object System.Windows.Forms.Label $lblInfo.Text = "Note: Backup uses 'pnputil /export-driver'. This script must run as Administrator." $lblInfo.AutoSize = $true $lblInfo.Font = $Theme.Font $lblInfo.Location = New-Object System.Drawing.Point(250, 525) $lblInfo.Anchor = 'Left,Bottom' $lblInfo.ForeColor = $Theme.Muted $form.Controls.Add($btnBackup) $form.Controls.Add($lblInfo) function Populate-Grid { param( [System.Windows.Forms.DataGridView]$Grid, [System.Collections.IEnumerable]$Drivers, [bool]$DefaultChecked = $true ) $Grid.Rows.Clear() foreach ($d in $Drivers) { $rowIndex = $Grid.Rows.Add() $row = $Grid.Rows[$rowIndex] $row.Cells[0].Value = $DefaultChecked $row.Cells[1].Value = $d.InfName $row.Cells[2].Value = $d.DeviceName $row.Cells[3].Value = $d.ProviderName $row.Cells[4].Value = $d.Manufacturer $row.Cells[5].Value = $d.DriverVersion $row.Cells[6].Value = if ($d.DriverDate -is [DateTime]) { $d.DriverDate.ToShortDateString() } elseif ($d.DriverDate) { [string]$d.DriverDate } else { "" } $row.Cells[7].Value = $d.DeviceClass $row.Cells[8].Value = $d.DeviceID $row.Cells[9].Value = if ($d.PSObject.Properties.Match('Status').Count -gt 0) { [string]$d.Status } else { "" } if ($d.PSObject.Properties.Match('InfPath').Count -gt 0) { $row.Tag = $d.InfPath } else { $row.Tag = $null } } } Populate-Grid -Grid $gridThird -Drivers $thirdPartyDrivers -DefaultChecked $true Populate-Grid -Grid $gridMs -Drivers $microsoftDrivers -DefaultChecked $false $gridThird.Add_ColumnHeaderMouseClick({ param($sender, $e) Handle-HeaderClick -Grid $sender -EventArgs $e }) $gridMs.Add_ColumnHeaderMouseClick({ param($sender, $e) Handle-HeaderClick -Grid $sender -EventArgs $e }) $gridRestore.Add_ColumnHeaderMouseClick({ param($sender, $e) Handle-HeaderClick -Grid $sender -EventArgs $e }) function Get-SelectedInfNames { param([System.Windows.Forms.DataGridView]$Grid) $list = @() foreach ($row in $Grid.Rows) { if ($row.Cells[0].Value -eq $true) { $inf = $row.Cells[1].Value if ($inf -and ($inf -is [string])) { $list += $inf } } } return $list } function Resolve-InfStringToken { param([string]$Value, [hashtable]$Strings) if (-not $Value) { return "" } $v = $Value.Trim() $v = $v.Trim('"') $v = [regex]::Replace($v, '%([^%]+)%', { param($m) $key = $m.Groups[1].Value if ($Strings -and $Strings.ContainsKey($key)) { return $Strings[$key] } return $m.Value }) return $v.Trim().Trim('"') } function Get-InfSectionValue { param( [string[]]$Lines, [string]$SectionName, [string]$KeyName ) if (-not $Lines) { return $null } $sec = $SectionName.Trim() $key = $KeyName.Trim() $in = $false foreach ($line0 in $Lines) { $line = ($line0 -replace ';.*$','').Trim() if (-not $line) { continue } if ($line -match '^\[(.+?)\]\s*$') { $in = ($matches[1].Trim() -ieq $sec) continue } if (-not $in) { continue } if ($line -match '^\s*' + [regex]::Escape($key) + '\s*=\s*(.+?)\s*$') { return $matches[1].Trim() } } return $null } function Get-InfStringsTable { param([string[]]$Lines) $tbl = @{} if (-not $Lines) { return $tbl } $in = $false foreach ($line0 in $Lines) { $line = ($line0 -replace ';.*$','').Trim() if (-not $line) { continue } if ($line -match '^\[(.+?)\]\s*$') { $section = $matches[1].Trim() $in = ($section -imatch '^Strings(?:\..+)?$') continue } if (-not $in) { continue } if ($line -match '^\s*([^=]+?)\s*=\s*(.+?)\s*$') { $k = $matches[1].Trim() $v = $matches[2].Trim().Trim('"') if (-not $tbl.ContainsKey($k)) { $tbl[$k] = $v } } } return $tbl } function Get-InfFirstHardwareId { param([string[]]$Lines) if (-not $Lines) { return "" } foreach ($line0 in $Lines) { $line = ($line0 -replace ';.*$','').Trim() if (-not $line) { continue } $m = [regex]::Match($line, '(?i)\b(PCI|USB|HID|ACPI|BTH|SWD|SCSI|IDE)\\[A-Z0-9_&\\\-\.]+') if ($m.Success) { $id = $m.Value.ToUpperInvariant() if ($id -like 'ROOT\*') { continue } if ($id -like 'HTREE\*') { continue } return $id } } return "" } function Get-InfFirstDeviceDescription { param([string[]]$Lines, [hashtable]$Strings) if (-not $Lines) { return "" } foreach ($line0 in $Lines) { $line = ($line0 -replace ';.*$','').Trim() if (-not $line -or $line.StartsWith('[')) { continue } if ($line -match '^\s*([^=]+?)\s*=\s*[^,]+,\s*((?:PCI|USB|HID|ACPI|BTH|SWD|SCSI|IDE)\\[^,\s]+)') { $desc = Resolve-InfStringToken -Value $matches[1].Trim() -Strings $Strings if ($desc -and $desc -notmatch '^%.*%$') { return $desc } } } return "" } function Get-RestoreDriverFromInf { param( [Parameter(Mandatory=$true)][string]$InfPath, [string]$FallbackProvider, [string]$FallbackDevice, [string]$FallbackCategory, [string]$Status = "" ) $lines = @() try { $lines = Get-Content -LiteralPath $InfPath -ErrorAction Stop } catch { $lines = @() } $strings = Get-InfStringsTable -Lines $lines $provRaw = Get-InfSectionValue -Lines $lines -SectionName 'Version' -KeyName 'Provider' $manuRaw = Get-InfSectionValue -Lines $lines -SectionName 'Version' -KeyName 'Manufacturer' $classRaw = Get-InfSectionValue -Lines $lines -SectionName 'Version' -KeyName 'Class' $verRaw = Get-InfSectionValue -Lines $lines -SectionName 'Version' -KeyName 'DriverVer' $provider = Resolve-InfStringToken -Value $provRaw -Strings $strings $manuf = Resolve-InfStringToken -Value $manuRaw -Strings $strings $class = Resolve-InfStringToken -Value $classRaw -Strings $strings if (-not $provider) { $provider = $FallbackProvider } if (-not $manuf) { $manuf = $provider } $device = $FallbackDevice if (-not $device) { $device = Get-InfFirstDeviceDescription -Lines $lines -Strings $strings } if (-not $device) { $device = (Split-Path $InfPath -LeafBase) } $driverDate = "" $driverVer = "" if ($verRaw) { $vr = Resolve-InfStringToken -Value $verRaw -Strings $strings $parts = $vr -split ',' if ($parts.Length -ge 1) { $driverDate = $parts[0].Trim() } if ($parts.Length -ge 2) { $driverVer = $parts[1].Trim() } } $deviceClass = if ($FallbackCategory) { $FallbackCategory } elseif ($class) { $class } else { "Other" } $hwid = Get-InfFirstHardwareId -Lines $lines [PSCustomObject]@{ InfName = (Split-Path $InfPath -Leaf) DeviceName = $device ProviderName = $provider Manufacturer = $manuf DriverVersion = $driverVer DriverDate = $driverDate DeviceClass = $deviceClass DeviceID = $hwid Status = $Status InfPath = $InfPath } } function Get-RestoreDriversFromFolder { param([Parameter(Mandatory=$true)][string]$RootFolder) if (-not (Test-Path -LiteralPath $RootFolder -PathType Container)) { return @() } $rootResolved = (Resolve-Path -LiteralPath $RootFolder).Path.TrimEnd('\') $infFiles = @(Get-ChildItem -LiteralPath $rootResolved -Recurse -File -Filter '*.inf' -Force -ErrorAction SilentlyContinue) if ($infFiles.Count -eq 0) { return @() } $out = @() foreach ($inf in $infFiles) { $infPath = $inf.FullName $fallbackProvider = "" $fallbackDevice = "" $fallbackCategory = "" $folderName = Split-Path $inf.DirectoryName -Leaf if ($folderName -match '^(.*?)\s*-\s*(.*?)\s*\[(.+?)\]\s*$') { $fallbackProvider = $matches[1].Trim() $fallbackDevice = $matches[2].Trim() } else { $fallbackDevice = $folderName } try { $rel = $inf.DirectoryName.Substring($rootResolved.Length).TrimStart('\') $segs = $rel -split '[\\/]' if ($segs.Length -ge 1) { if ($segs[0] -in @('Microsoft','ThirdParty')) { if ($segs.Length -ge 2) { $fallbackCategory = $segs[1] } } else { $fallbackCategory = $segs[0] } } } catch {} $out += Get-RestoreDriverFromInf -InfPath $infPath -FallbackProvider $fallbackProvider -FallbackDevice $fallbackDevice -FallbackCategory $fallbackCategory -Status "Restore Source" } return $out } function Get-SelectedRestoreItems { param([System.Windows.Forms.DataGridView]$Grid) $items = @() foreach ($row in $Grid.Rows) { if ($row.Cells[0].Value -eq $true) { $path = $row.Tag if ($path -and ($path -is [string])) { $items += [PSCustomObject]@{ InfPath = $path DeviceName = [string]$row.Cells[2].Value InfName = [string]$row.Cells[1].Value } } } } return $items } function Get-OfflineWindowsDriverList { param([Parameter(Mandatory=$true)][string]$WindowsRoot) if ([string]::IsNullOrWhiteSpace($WindowsRoot)) { return @() } if (-not (Test-Path -LiteralPath $WindowsRoot -PathType Container)) { [System.Windows.Forms.MessageBox]::Show( "Selected folder does not exist:`n$WindowsRoot", "Offline Windows", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return @() } $system32 = Join-Path $WindowsRoot "System32" if (-not (Test-Path -LiteralPath $system32 -PathType Container)) { [System.Windows.Forms.MessageBox]::Show( "The selected folder does not look like a Windows folder.`n`nExpected subfolder not found:`n$system32", "Offline Windows", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return @() } $scanRoots = @() $driverStore = Join-Path $WindowsRoot "System32\DriverStore\FileRepository" $infRoot = Join-Path $WindowsRoot "INF" if (Test-Path -LiteralPath $driverStore -PathType Container) { $scanRoots += $driverStore } if (Test-Path -LiteralPath $infRoot -PathType Container) { $scanRoots += $infRoot } if ($scanRoots.Count -eq 0) { [System.Windows.Forms.MessageBox]::Show( "No driver INF folders were found under the selected Windows folder.", "Offline Windows", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) return @() } $infFiles = @() foreach ($root in $scanRoots) { $infFiles += @(Get-ChildItem -LiteralPath $root -Recurse -File -Filter '*.inf' -Force -ErrorAction SilentlyContinue) } if ($infFiles.Count -eq 0) { return @() } $seen = @{} $out = @() foreach ($inf in $infFiles) { $full = $inf.FullName $key = $full.ToLowerInvariant() if ($seen.ContainsKey($key)) { continue } $seen[$key] = $true $fallbackProvider = "" $fallbackDevice = "" $fallbackCategory = "" $folderName = Split-Path $inf.DirectoryName -Leaf if ($folderName -match '^(.*?)\s*-\s*(.*?)\s*\[(.+?)\]\s*$') { $fallbackProvider = $matches[1].Trim() $fallbackDevice = $matches[2].Trim() } else { $fallbackDevice = $folderName } try { if ($full.StartsWith($driverStore, [System.StringComparison]::OrdinalIgnoreCase)) { $fallbackCategory = "DriverStore" } elseif ($full.StartsWith($infRoot, [System.StringComparison]::OrdinalIgnoreCase)) { $fallbackCategory = "INF" } } catch {} $out += Get-RestoreDriverFromInf -InfPath $full -FallbackProvider $fallbackProvider -FallbackDevice $fallbackDevice -FallbackCategory $fallbackCategory -Status "Offline" } return $out } function Get-CurrentWindowsNotPresentDrivers { param([System.Collections.IEnumerable]$PresentDrivers) $presentInf = @{} foreach ($d in $PresentDrivers) { if (-not [string]::IsNullOrWhiteSpace($d.InfName)) { $presentInf[$d.InfName.ToLowerInvariant()] = $true } } $infRoot = Join-Path $env:WINDIR 'INF' if (-not (Test-Path -LiteralPath $infRoot -PathType Container)) { return @() } $infFiles = @(Get-ChildItem -LiteralPath $infRoot -File -Filter 'oem*.inf' -Force -ErrorAction SilentlyContinue) $out = @() foreach ($inf in $infFiles) { if ($presentInf.ContainsKey($inf.Name.ToLowerInvariant())) { continue } $drv = Get-RestoreDriverFromInf -InfPath $inf.FullName -Status "Not Present" if ($null -eq $drv) { continue } if ($drv.ProviderName -and ($drv.ProviderName -like '*Microsoft*')) { continue } $out += $drv } return $out } function Rebuild-DriverIndex { param([System.Collections.IEnumerable]$Drivers) $script:driverByInf = @{} foreach ($d in $Drivers) { if ([string]::IsNullOrWhiteSpace($d.InfName)) { continue } if (-not $script:driverByInf.ContainsKey($d.InfName)) { $script:driverByInf[$d.InfName] = $d } } } function Load-DriverSource { param( [ValidateSet('Current','Offline')][string]$Mode, [string]$WindowsRoot, [bool]$ShowMessage = $true ) $drivers = @() if ($Mode -eq 'Current') { $presentDrivers = @(Get-DriverList) $drivers = @($presentDrivers) if ($chkShowNotPresent -and $chkShowNotPresent.Checked) { $drivers += @(Get-CurrentWindowsNotPresentDrivers -PresentDrivers $presentDrivers) } $script:DriverSourceMode = 'Current' $script:OfflineWindowsRoot = $null $lblSourceStatus.Text = if ($chkShowNotPresent -and $chkShowNotPresent.Checked) { "Source: running Windows + stored OEM drivers" } else { "Source: running Windows" } $lblInfo.Text = "Note: Backup uses 'pnputil /export-driver'. Not Present entries are third-party oem*.inf packages stored under Windows\INF." } else { $drivers = @(Get-OfflineWindowsDriverList -WindowsRoot $WindowsRoot) if (-not $drivers -or $drivers.Count -eq 0) { return } $script:DriverSourceMode = 'Offline' $script:OfflineWindowsRoot = $WindowsRoot $lblSourceStatus.Text = "Offline: " + (Split-Path $WindowsRoot -Leaf) $lblInfo.Text = "Note: Offline Windows mode lists INF packages from the selected Windows folder. Backup copies the selected package folders." } $script:allDrivers = $drivers $script:thirdPartyDrivers = $drivers | Where-Object { $_.ProviderName -notlike "*Microsoft*" } $script:microsoftDrivers = $drivers | Where-Object { $_.ProviderName -like "*Microsoft*" } Rebuild-DriverIndex -Drivers $drivers Populate-Grid -Grid $gridThird -Drivers $script:thirdPartyDrivers -DefaultChecked $true Populate-Grid -Grid $gridMs -Drivers $script:microsoftDrivers -DefaultChecked $false foreach ($c in $gridThird.Columns) { if ($c.Index -ne 0) { $c.HeaderCell.SortGlyphDirection = [System.Windows.Forms.SortOrder]::None } } foreach ($c in $gridMs.Columns) { if ($c.Index -ne 0) { $c.HeaderCell.SortGlyphDirection = [System.Windows.Forms.SortOrder]::None } } if ($ShowMessage) { [System.Windows.Forms.MessageBox]::Show( "Drivers loaded: $($drivers.Count)`nThird-party: $($script:thirdPartyDrivers.Count)`nMicrosoft: $($script:microsoftDrivers.Count)", "Driver source", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) } } function Get-SelectedOfflineDriverItems { param([System.Windows.Forms.DataGridView[]]$Grids) $items = @() foreach ($Grid in $Grids) { foreach ($row in $Grid.Rows) { if ($row.Cells[0].Value -eq $true) { $path = $row.Tag if ($path -and ($path -is [string]) -and (Test-Path -LiteralPath $path -PathType Leaf)) { $items += [PSCustomObject]@{ InfPath = $path InfName = [string]$row.Cells[1].Value DeviceName = [string]$row.Cells[2].Value ProviderName = [string]$row.Cells[3].Value Manufacturer = [string]$row.Cells[4].Value DriverVersion = [string]$row.Cells[5].Value DriverDate = [string]$row.Cells[6].Value DeviceClass = [string]$row.Cells[7].Value DeviceID = [string]$row.Cells[8].Value Status = [string]$row.Cells[9].Value } } } } } return $items } function Copy-DirectoryContents { param( [Parameter(Mandatory=$true)][string]$Source, [Parameter(Mandatory=$true)][string]$Destination ) if (-not (Test-Path -LiteralPath $Destination -PathType Container)) { New-Item -ItemType Directory -Path $Destination -Force | Out-Null } $children = @(Get-ChildItem -LiteralPath $Source -Force -ErrorAction Stop) foreach ($child in $children) { Copy-Item -LiteralPath $child.FullName -Destination $Destination -Recurse -Force -ErrorAction Stop } } $rbCurrentWindows.Add_CheckedChanged({ if ($rbCurrentWindows.Checked) { $txtOfflineWindows.Enabled = $false $btnBrowseOfflineWindows.Enabled = $false $chkShowNotPresent.Enabled = $true Load-DriverSource -Mode 'Current' } }) $rbOfflineWindows.Add_CheckedChanged({ if ($rbOfflineWindows.Checked) { $txtOfflineWindows.Enabled = $true $btnBrowseOfflineWindows.Enabled = $true $chkShowNotPresent.Enabled = $false $script:DriverSourceMode = 'Offline' } }) $chkShowNotPresent.Add_CheckedChanged({ if ($script:DriverSourceMode -eq 'Current' -and $rbCurrentWindows.Checked) { Load-DriverSource -Mode 'Current' -ShowMessage:$false } }) $btnBrowseOfflineWindows.Add_Click({ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "Select the offline Windows folder, for example D:\Windows" if (-not [string]::IsNullOrWhiteSpace($txtOfflineWindows.Text) -and (Test-Path -LiteralPath $txtOfflineWindows.Text -PathType Container)) { $dialog.SelectedPath = $txtOfflineWindows.Text } if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return } $txtOfflineWindows.Text = $dialog.SelectedPath $rbOfflineWindows.Checked = $true Load-DriverSource -Mode 'Offline' -WindowsRoot $txtOfflineWindows.Text }) $btnReloadSource.Add_Click({ if ($rbOfflineWindows.Checked) { if ([string]::IsNullOrWhiteSpace($txtOfflineWindows.Text)) { [System.Windows.Forms.MessageBox]::Show( "Please select an offline Windows folder first, for example D:\Windows.", "Offline Windows", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return } Load-DriverSource -Mode 'Offline' -WindowsRoot $txtOfflineWindows.Text } else { Load-DriverSource -Mode 'Current' } }) $btnBackup.Add_Click({ if ($script:DriverSourceMode -eq 'Offline') { $items = @(Get-SelectedOfflineDriverItems -Grids @($gridThird, $gridMs)) if (-not $items -or $items.Count -eq 0) { [System.Windows.Forms.MessageBox]::Show( "You have not selected any offline driver package to backup.", "Warning", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return } $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "Select the root folder where the offline driver packages will be copied" $dialog.SelectedPath = [Environment]::GetFolderPath("Desktop") if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return } $rootDest = $dialog.SelectedPath $okCount = 0 $failCount = 0 $errors = @() $total = $items.Count $progressForm = New-Object System.Windows.Forms.Form $progressForm.Text = "Copying offline driver packages..." $progressForm.Size = New-Object System.Drawing.Size(480, 150) $progressForm.StartPosition = 'CenterParent' $progressForm.FormBorderStyle = 'FixedDialog' Style-ProgressForm -Form $progressForm $progressForm.ControlBox = $false $progressForm.TopMost = $true $lblProg = New-Object System.Windows.Forms.Label $lblProg.AutoSize = $true $lblProg.Location = New-Object System.Drawing.Point(12, 10) $lblProg.Text = "Copying offline driver packages... (0 of $total)" $progressForm.Controls.Add($lblProg) $pb = New-Object System.Windows.Forms.ProgressBar $pb.Location = New-Object System.Drawing.Point(12, 35) $pb.Size = New-Object System.Drawing.Size(450, 20) $pb.Style = 'Continuous' $pb.Minimum = 0 $pb.Maximum = $total $progressForm.Controls.Add($pb) $lblDetail = New-Object System.Windows.Forms.Label $lblDetail.AutoSize = $true $lblDetail.Location = New-Object System.Drawing.Point(12, 65) $lblDetail.Text = "Current package: -" $progressForm.Controls.Add($lblDetail) $progressForm.Show() [System.Windows.Forms.Application]::DoEvents() $index = 0 try { foreach ($it in $items) { $index++ $devLabel = if ($it.DeviceName) { $it.DeviceName } else { $it.InfName } $lblProg.Text = "Copying offline driver packages... ($index of $total)" $lblDetail.Text = "Current package: $devLabel" if ($index -le $pb.Maximum) { $pb.Value = $index } [System.Windows.Forms.Application]::DoEvents() try { $infPath = $it.InfPath if ([string]::IsNullOrWhiteSpace($infPath) -or -not (Test-Path -LiteralPath $infPath -PathType Leaf)) { throw "INF not found: $infPath" } $packageFolder = Split-Path $infPath -Parent $isMicrosoft = $false if ($it.ProviderName -and ($it.ProviderName -like "*Microsoft*")) { $isMicrosoft = $true } $vendorRootName = if ($isMicrosoft) { "Microsoft" } else { "ThirdParty" } $vendorRootFolder = Join-Path $rootDest $vendorRootName $catName = Get-CategoryName $it.DeviceClass $catName = Sanitize-Name $catName $catFolder = Join-Path $vendorRootFolder $catName $provider = if ($it.ProviderName) { $it.ProviderName } else { "UnknownProvider" } $device = if ($it.DeviceName) { $it.DeviceName } else { "UnknownDevice" } $infName = if ($it.InfName) { $it.InfName } else { Split-Path $infPath -Leaf } $folderNameRaw = "{0} - {1} [{2}]" -f $provider, $device, $infName $folderName = Sanitize-Name $folderNameRaw $driverDest = Join-Path $catFolder $folderName Copy-DirectoryContents -Source $packageFolder -Destination $driverDest $okCount++ } catch { $failCount++ $errors += "$($it.InfName) : $($_.Exception.Message)" } } } finally { if ($progressForm -and -not $progressForm.IsDisposed) { $progressForm.Close() $progressForm.Dispose() } } $msg = "Offline Windows folder: $script:OfflineWindowsRoot`nBackup root folder: $rootDest`n`nSuccessful: $okCount`nFailed: $failCount" if ($errors.Count -gt 0) { $msg += "`n`nErrors:`n" + ($errors -join "`n") } [System.Windows.Forms.MessageBox]::Show( $msg, "Offline backup finished", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) return } $selected = @() $selected += Get-SelectedInfNames -Grid $gridThird $selected += Get-SelectedInfNames -Grid $gridMs $selected = $selected | Sort-Object -Unique if (-not $selected -or $selected.Count -eq 0) { [System.Windows.Forms.MessageBox]::Show( "You have not selected any driver to backup.", "Warning", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return } $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "Select the root folder where the driver backups will be stored" $dialog.SelectedPath = [Environment]::GetFolderPath("Desktop") if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return } $rootDest = $dialog.SelectedPath $okCount = 0 $failCount = 0 $errors = @() $total = $selected.Count $progressForm = New-Object System.Windows.Forms.Form $progressForm.Text = "Backing up drivers..." $progressForm.Size = New-Object System.Drawing.Size(460, 150) $progressForm.StartPosition = 'CenterParent' $progressForm.FormBorderStyle = 'FixedDialog' Style-ProgressForm -Form $progressForm $progressForm.ControlBox = $false $progressForm.TopMost = $true $lblProg = New-Object System.Windows.Forms.Label $lblProg.AutoSize = $true $lblProg.Location = New-Object System.Drawing.Point(12, 10) $lblProg.Text = "Backing up drivers... (0 of $total)" $progressForm.Controls.Add($lblProg) $pb = New-Object System.Windows.Forms.ProgressBar $pb.Location = New-Object System.Drawing.Point(12, 35) $pb.Size = New-Object System.Drawing.Size(430, 20) $pb.Style = 'Continuous' $pb.Minimum = 0 $pb.Maximum = $total $progressForm.Controls.Add($pb) $lblDetail = New-Object System.Windows.Forms.Label $lblDetail.AutoSize = $true $lblDetail.Location = New-Object System.Drawing.Point(12, 65) $lblDetail.Text = "Current device: -" $progressForm.Controls.Add($lblDetail) $progressForm.Show() [System.Windows.Forms.Application]::DoEvents() $index = 0 try { foreach ($inf in $selected) { $index++ $drv = $null if (-not [string]::IsNullOrWhiteSpace($inf)) { $drv = $driverByInf[$inf] } $devLabel = if ($drv -and $drv.DeviceName) { $drv.DeviceName } elseif ($drv -and $drv.ProviderName) { "$($drv.ProviderName) ($inf)" } else { "INF: $inf" } $lblProg.Text = "Backing up drivers... ($index of $total)" $lblDetail.Text = "Current device: $devLabel" if ($index -le $pb.Maximum) { $pb.Value = $index } [System.Windows.Forms.Application]::DoEvents() if ([string]::IsNullOrWhiteSpace($inf)) { $failCount++ $errors += "NULL INF: cannot export this entry." continue } if ($null -eq $drv) { $failCount++ $errors += "$inf : Driver metadata not found." continue } $isMicrosoft = $false if ($drv.ProviderName -and ($drv.ProviderName -like "*Microsoft*")) { $isMicrosoft = $true } $vendorRootName = if ($isMicrosoft) { "Microsoft" } else { "ThirdParty" } $vendorRootFolder = Join-Path $rootDest $vendorRootName $catName = Get-CategoryName $drv.DeviceClass $catName = Sanitize-Name $catName $catFolder = Join-Path $vendorRootFolder $catName $provider = if ($drv.ProviderName) { $drv.ProviderName } else { "UnknownProvider" } $device = if ($drv.DeviceName) { $drv.DeviceName } else { "UnknownDevice" } $folderNameRaw = "{0} - {1} [{2}]" -f $provider, $device, $drv.InfName $folderName = Sanitize-Name $folderNameRaw $driverDest = Join-Path $catFolder $folderName try { if (-not (Test-Path $driverDest)) { New-Item -ItemType Directory -Path $driverDest -Force | Out-Null } $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = "pnputil.exe" $psi.Arguments = "/export-driver `"$inf`" `"$driverDest`"" $psi.CreateNoWindow = $true $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $proc = [System.Diagnostics.Process]::Start($psi) $proc.WaitForExit() if ($proc.ExitCode -eq 0) { $okCount++ } else { $failCount++ $err = $proc.StandardError.ReadToEnd() if ([string]::IsNullOrWhiteSpace($err)) { $err = $proc.StandardOutput.ReadToEnd() } $errors += "$inf : $err" } } catch { $failCount++ $errors += "$inf : $($_.Exception.Message)" } } } finally { if ($progressForm -and -not $progressForm.IsDisposed) { $progressForm.Close() $progressForm.Dispose() } } $msg = "Backup root folder: $rootDest`n`nSuccessful: $okCount`nFailed: $failCount" if ($errors.Count -gt 0) { $msg += "`n`nErrors:`n" + ($errors -join "`n") } [System.Windows.Forms.MessageBox]::Show( $msg, "Backup finished", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) }) $btnSelectRestore.Add_Click({ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "Select the backup root folder to scan for INF files (subfolders will be scanned)" $dialog.SelectedPath = [Environment]::GetFolderPath("Desktop") if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return } $script:RestoreRoot = $dialog.SelectedPath $drivers = @(Get-RestoreDriversFromFolder -RootFolder $script:RestoreRoot) if (-not $drivers -or $drivers.Count -eq 0) { $gridRestore.Rows.Clear() [System.Windows.Forms.MessageBox]::Show( "No INF files were found under:`n$($script:RestoreRoot)", "Restore", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) return } Populate-Grid -Grid $gridRestore -Drivers $drivers -DefaultChecked $false }) $btnInstall.Add_Click({ $items = @(Get-SelectedRestoreItems -Grid $gridRestore) if (-not $items -or $items.Count -eq 0) { [System.Windows.Forms.MessageBox]::Show( "You have not selected any driver to install.", "Warning", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return } $okCount = 0 $failCount = 0 $errors = @() $total = $items.Count $progressForm = New-Object System.Windows.Forms.Form $progressForm.Text = "Installing drivers..." $progressForm.Size = New-Object System.Drawing.Size(460, 150) $progressForm.StartPosition = 'CenterParent' $progressForm.FormBorderStyle = 'FixedDialog' Style-ProgressForm -Form $progressForm $progressForm.ControlBox = $false $progressForm.TopMost = $true $lblProg = New-Object System.Windows.Forms.Label $lblProg.AutoSize = $true $lblProg.Location = New-Object System.Drawing.Point(12, 10) $lblProg.Text = "Installing drivers... (0 of $total)" $progressForm.Controls.Add($lblProg) $pb = New-Object System.Windows.Forms.ProgressBar $pb.Location = New-Object System.Drawing.Point(12, 35) $pb.Size = New-Object System.Drawing.Size(430, 20) $pb.Style = 'Continuous' $pb.Minimum = 0 $pb.Maximum = $total $progressForm.Controls.Add($pb) $lblDetail = New-Object System.Windows.Forms.Label $lblDetail.AutoSize = $true $lblDetail.Location = New-Object System.Drawing.Point(12, 65) $lblDetail.Text = "Current device: -" $progressForm.Controls.Add($lblDetail) $progressForm.Show() [System.Windows.Forms.Application]::DoEvents() $index = 0 try { foreach ($it in $items) { $index++ $infPath = $it.InfPath $devLabel = if ($it.DeviceName) { $it.DeviceName } else { $it.InfName } $lblProg.Text = "Installing drivers... ($index of $total)" $lblDetail.Text = "Current device: $devLabel" if ($index -le $pb.Maximum) { $pb.Value = $index } [System.Windows.Forms.Application]::DoEvents() if ([string]::IsNullOrWhiteSpace($infPath) -or -not (Test-Path -LiteralPath $infPath -PathType Leaf)) { $failCount++ $errors += "$($it.InfName) : INF not found: $infPath" continue } try { $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = "pnputil.exe" $psi.Arguments = "/add-driver `"$infPath`" /install" $psi.CreateNoWindow = $true $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $proc = [System.Diagnostics.Process]::Start($psi) $proc.WaitForExit() if ($proc.ExitCode -eq 0) { $okCount++ } else { $failCount++ $err = $proc.StandardError.ReadToEnd() if ([string]::IsNullOrWhiteSpace($err)) { $err = $proc.StandardOutput.ReadToEnd() } $errors += "$($it.InfName) : $err" } } catch { $failCount++ $errors += "$($it.InfName) : $($_.Exception.Message)" } } } finally { if ($progressForm -and -not $progressForm.IsDisposed) { $progressForm.Close() $progressForm.Dispose() } } $msg = "Selected restore folder: $script:RestoreRoot`n`nSuccessful: $okCount`nFailed: $failCount" if ($errors.Count -gt 0) { $msg += "`n`nErrors:`n" + ($errors -join "`n") } [System.Windows.Forms.MessageBox]::Show( $msg, "Install finished", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) }) [void]$form.ShowDialog()