PowerShell调用msiexec卸载软件时数组越界错误排查
问题解决:PowerShell脚本调用msiexec卸载时数组越界错误
执行自定义PowerShell脚本卸载软件时,运行命令:
doRemoveMSI -msi "msiexec.exe" -arguments "/x {EFF8AB9B-9551-4994-9FD2-D40A5A2E9B31}"
触发"Index was outside the bounds of the array"数组越界错误,虽软件卸载成功,但需修复该报错。
错误日志
PS C:\Windows\system32> doRemoveMSI -msi "msiexec.exe" -arguments "/x {EFF8AB9B-9551-4994-9FD2-D40A5A2E9B31}" Index was outside the bounds of the array. At line:123 char:65 + ... Name doRemoveMSI: $msi, a0 = '$($arguments[0]),a1='$($arguments[1])'" + ~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (:) [], IndexOutOfRangeException + FullyQualifiedErrorId : System.IndexOutOfRangeException Remove-Symantec doRemoveMSI: msiexec.exe, a0 = '/x {EFF8AB9B-9551-4994-9FD2-D40A5A2E9B31},a1='' Remove-Symantec doRemoveMSI: successful result is System.Diagnostics.Process, exitcode = 0 Remove-Symantec doRemoveMSI: success, return = 0 Remove-Symantec doRemoveMSI: exit 0
脚本代码
[CmdletBinding()] ## ## rem-sep.ps1 ## [String] $logname = 'Application' [String] $source = 'IntuneWinApp' [String] $pName = 'Remove-Symantec' Set-StrictMode -Version Latest Set-Variable evtError 1 Set-Variable evtWarning 2 Set-Variable evtInformation 4 Set-Variable evtAuditSuccess 8 Set-Variable evtAuditFailure 16 function NewMyEventlogSource { Param ( [string] $source, [string] $logname ) [bool] $success = $false try { $success = [System.Diagnostics.EventLog]::SourceExists( $source ) } catch [ System.Security.SecurityException ] { ## we aren't running as a privileged account so we ## couldn't search the security log (and maybe others). ## we have no choice but to try to create the $source. ## however, that will probably also fail. } if( -not $success ) { try { [System.Diagnostics.EventLog]::CreateEventSource( $source, $logname ) $success = $true } catch { $e = $_ Write-Warning "Cannot create event source '$source', error $e" } } $success } function WriteMyEventlog { Param ( [ValidateNotNullOrEmpty()] [String] $logname, [ValidateNotNullOrEmpty()] [String] $source, [ValidateNotNullOrEmpty()] [String] $message, [ValidateSet( 1, 2, 4, 8, 16 )] [Int] $type = $evtInformation, [ValidateRange( 1, 65535 )] [Int] $eventId = 1, [ValidateRange( 0, 32767 )] [Int16] $category = 0, [Bool] $override = $false ) <# if( [String]::IsNullOrEmpty( $message ) ) { $ex = New-Object System.ArgumentException( "Message parameter may not be null or empty" ) throw $ex } #> if( NewMyEventlogSource -source $source -logname $logname ) { [System.Diagnostics.EventLog]::WriteEntry( $source, $message, $type, ## [System.Diagnostics.EventLog]::EventLogEntryType enum $eventId, $category ) } elseif( $override ) { [System.Diagnostics.EventLog]::WriteEntry( 'Application', ## 'Application' always exists in the Application log $message, $type, $eventId, $category ) } } function log { $str = $args -join ' ' Write-Host $str WriteMyEventlog -logname $logname -source $source ` -message $str ` -type $evtInformation ` -eventid 1 ` -category 0 ` -override $true } function logE { $str = $args -join ' ' Write-Host $str WriteMyEventlog -logname $logname -source $source ` -message $str ` -type $evtError ` -eventid 99 ` -category 0 ` -override $true } function doRemoveMSI { Param ( [String] $msi, [String[]] $arguments ) log "$pName doRemoveMSI: $msi, a0 = '$( $arguments[ 0 ] )', a1 = '$( $arguments[ 1 ] )'" <# $fileExists = Test-Path $arguments[ 1 ] -PathType Leaf if( -not $fileExists ) { logE "$pName doMSI: file doesn't exist '$( $arguments[ 1 ] )'" } #> $result = Start-Process -Wait -Verb RunAs -FilePath $msi -ArgumentList $arguments -PassThru -WorkingDirectory $pwd.Path if( $? ) { $return = 0 if( $null -eq $result ) { log "$pName successful result from Start-Process is null" } else { if( $result -is [System.Diagnostics.Process] ) { $return = $result.ExitCode log "$pName doRemoveMSI: successful result is System.Diagnostics.Process, exitcode = $return" if( $return -eq 1619 ) { logE "$pName doRemoveMSI: a lie! msiexec error 1619 on '$( $arguments[ 0 ] )' '$( $arguments[ 1 ] )'" } } else { $return = $result.ToString() log "$pName doRemoveMSI: successful result is type $( $result.GetType().FullName )" } } log "$pName doRemoveMSI: success, return = $return" } else { $e = $error[ 0 ] logE "$pName doRemoveMSI: error $e" $return = -1 } log "$pName doRemoveMSI: exit" return $return } ##WriteMyEventlog $logname $source $message $type $eventId $category $override
问题原因
doRemoveMSI函数中$arguments参数定义为[String[]]数组类型,但调用时传入的是单个字符串,PowerShell自动将其转换为仅包含1个元素的数组。- 代码中直接访问
$arguments[1](日志输出、注释的文件检查逻辑、1619错误判断逻辑),但数组只有1个元素,索引1不存在,触发越界错误。
解决方案
方案1:修改日志与判断逻辑,兼容任意数量参数
将doRemoveMSI函数内的日志输出、错误判断逻辑改为动态遍历数组元素,避免硬编码索引:
function doRemoveMSI { Param ( [String] $msi, [String[]] $arguments ) # 动态生成参数日志,避免硬编码索引 $argLog = @() for ($i=0; $i -lt $arguments.Count; $i++) { $argLog += "a$i = '$($arguments[$i])'" } $argLogStr = $argLog -join ', ' log "$pName doRemoveMSI: $msi, $argLogStr" $result = Start-Process -Wait -Verb RunAs -FilePath $msi -ArgumentList $arguments -PassThru -WorkingDirectory $pwd.Path if( $? ) { $return = 0 if( $null -eq $result ) { log "$pName successful result from Start-Process is null" } else { if( $result -is [System.Diagnostics.Process] ) { $return = $result.ExitCode log "$pName doRemoveMSI: successful result is System.Diagnostics.Process, exitcode = $return" if( $return -eq 1619 ) { # 拼接所有参数输出,避免访问不存在的索引 $argsStr = $arguments -join ' ' logE "$pName doRemoveMSI: a lie! msiexec error 1619 on '$argsStr'" } } else { $return = $result.ToString() log "$pName doRemoveMSI: successful result is type $( $result.GetType().FullName )" } } log "$pName doRemoveMSI: success, return = $return" } else { $e = $error[ 0 ] logE "$pName doRemoveMSI: error $e" $return = -1 } log "$pName doRemoveMSI: exit" return $return }
方案2:调用时传入数组格式参数
如果需要保留原脚本逻辑,可以在调用时将参数拆分为数组传递:
doRemoveMSI -msi "msiexec.exe" -arguments @("/x", "{EFF8AB9B-9551-4994-9FD2-D40A5A2E9B31}")
这样$arguments数组会包含2个元素,$arguments[0]为/x,$arguments[1]为产品GUID,避免越界。
额外优化建议
- 启用
Set-StrictMode -Version Latest后,未初始化的变量或不存在的数组索引会触发错误,建议对数组元素访问前先判断索引是否存在:if ($arguments.Count -gt 1) { ... $arguments[1] ... } - 注释掉的文件检查逻辑(
Test-Path $arguments[1])也存在同样的越界问题,若需要启用需先判断数组长度。
内容的提问来源于stack exchange,提问作者Arbelac
相关产品推荐
相关产品推荐

