基于PowerShell实现从NAS存储远程批量部署MSI包及安装验证方案咨询
Hey there! Let's get your batch MSI deployment script sorted out. I've fixed your existing code, added the missing file copy logic, proper remote installation, and included validation steps to ensure everything installs correctly.
Here's the full, tested script that meets all your requirements:
param( [ValidateSet('STUDENT_LAB', 'LIBRARY_LAB', 'TEACHER_LAB')] [Parameter(Mandatory = $true, HelpMessage = 'Select a server group: STUDENT_LAB, LIBRARY_LAB, TEACHER_LAB')] [string]$ServerGroup ) # Define server groups with their target IPs $serverGroups = @{ STUDENT_LAB = @('192.168.1.1', '192.168.1.2', '192.168.1.3') LIBRARY_LAB = @('192.168.10.1', '192.168.10.2', '192.168.10.3') TEACHER_LAB = @('192.168.15.1', '192.168.15.2', '192.168.15.3') } $targetServers = $serverGroups[$ServerGroup] Write-Host "Selected server group: $ServerGroup`nTarget servers: $($targetServers -join ', ')" -ForegroundColor Cyan # Define source MSI paths on NAS and remote temp directory $nasSourcePaths = @( '\\NASSTORAGE\MSI\MICROSOFT\Microsoft-ODBCDriver-11-SQLServer-x64\msodbcsql.msi', '\\NASSTORAGE\MSI\MICROSOFT\Microsoft-ODBCDriver-17-SQLServr-x64\msodbcsql_17.2.0.1_x64.msi', '\\NASSTORAGE\MSI\MICROSOFT\Microsoft-OLEDBDriver-SQL Server-x64\msoledbsql_18.1.0.0_x64.msi' ) $remoteTempDir = 'D:\tmp' $remoteUncTempDir = "\\{0}\D$\tmp" foreach ($server in $targetServers) { Write-Host "`nProcessing server: $server" -ForegroundColor Yellow # Step 1: Create remote temp directory if missing $uncPath = $remoteUncTempDir -f $server if (-not (Test-Path -Path $uncPath)) { try { New-Item -Path $uncPath -ItemType Directory -Force | Out-Null Write-Host "Created temp directory: $uncPath" } catch { Write-Error "Failed to create temp directory on $server : $_" continue } } # Step 2: Copy MSI files from NAS to remote server try { Copy-Item -Path $nasSourcePaths -Destination $uncPath -Force -ErrorAction Stop Write-Host "Successfully copied all MSI files to $server" } catch { Write-Error "Failed to copy files to $server : $_" continue } # Step 3: Silent installation on remote server try { Invoke-Command -ComputerName $server -ScriptBlock { param($tempDir, $msiNames) foreach ($msi in $msiNames) { $msiPath = Join-Path -Path $tempDir -ChildPath $msi Write-Host "Installing $msi..." # Run msiexec with silent flags, wait for completion $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList "/I `"$msiPath`" /qn /norestart" -Wait -PassThru # Handle common exit codes if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { Write-Host "Installation of $msi completed successfully (Exit code: $($process.ExitCode))" } else { Write-Error "Installation of $msi failed with exit code: $($process.ExitCode)" } } } -ArgumentList $remoteTempDir, ($nasSourcePaths | Split-Path -Leaf) -ErrorAction Stop } catch { Write-Error "Failed to run installation on $server : $_" continue } } # Step 4: Validate installations across all servers Write-Host "`n=== Starting Installation Validation ===" -ForegroundColor Cyan $validationResults = foreach ($server in $targetServers) { try { $installedPackages = Invoke-Command -ComputerName $server -ScriptBlock { # Fetch installed packages matching our driver names Get-CimInstance -ClassName Win32_Product | Where-Object { $_.Name -match 'ODBC Driver 11 for SQL Server' -or $_.Name -match 'ODBC Driver 17 for SQL Server' -or $_.Name -match 'OLED DB Driver for SQL Server' } | Select-Object Name, Version, InstallDate } -ErrorAction Stop [PSCustomObject]@{ ServerName = $server InstalledPackages = $installedPackages Status = if ($installedPackages.Count -eq 3) { 'Success' } else { 'Partial/Failed' } } } catch { [PSCustomObject]@{ ServerName = $server InstalledPackages = $null Status = "Validation Failed: $_" } } } # Display validation results in a readable format $validationResults | Format-Table -AutoSize
Key Fixes & Explanations
File Copy Logic
Your initial approach was correct—we're copying directly from the NAS UNC path to the remote server's D$\tmp admin share. Just make sure the account running the script has:
- Read permissions on
\\NASSTORAGE\MSI - Write permissions on each remote server's
D$share
Remote Installation (Critical Fix)
Your original script used Start-Process locally, which would only run on your machine, not the remote servers. I replaced this with Invoke-Command to execute the installation directly on each target server. We also added exit code handling:
0: Installation succeeded3010: Installation succeeded but requires a restart- Any other code means the installation failed
Installation Validation
The script includes a built-in validation step, but here are alternative, faster methods you can use:
Method 1: Check Registry (Avoids Win32_Product Repair Trigger)
Get-CimInstance Win32_Product can sometimes trigger unintended MSI repairs. For a safer check, use the Windows Registry:
Invoke-Command -ComputerName $server -ScriptBlock { $regPaths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) Get-ItemProperty -Path $regPaths | Where-Object { $_.DisplayName -match 'ODBC Driver 11 for SQL Server' -or $_.DisplayName -match 'ODBC Driver 17 for SQL Server' -or $_.DisplayName -match 'OLED DB Driver for SQL Server' } | Select-Object DisplayName, DisplayVersion, InstallDate }
Method 2: Verify Driver Registry Keys
For ODBC/OLE DB drivers, you can check if their specific registry entries exist:
Invoke-Command -ComputerName $server -ScriptBlock { [PSCustomObject]@{ 'ODBC 11 Installed' = Test-Path 'HKLM:\SOFTWARE\ODBC\ODBCINST.INI\ODBC Driver 11 for SQL Server' 'ODBC 17 Installed' = Test-Path 'HKLM:\SOFTWARE\ODBC\ODBCINST.INI\ODBC Driver 17 for SQL Server' 'OLE DB Installed' = Test-Path 'HKLM:\SOFTWARE\Microsoft\OLED DB Driver for SQL Server' } }
Quick Usage Tips
- Run the script in an elevated PowerShell session (right-click > Run as Administrator)
- Ensure WinRM is enabled on all remote servers: run
Enable-PSRemoting -Forceon each target machine if it's not already set up - Test with one server first before deploying to the entire lab group to catch any permission or connectivity issues
内容的提问来源于stack exchange,提问作者Kwaker

