You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

  1. doRemoveMSI函数中$arguments参数定义为[String[]]数组类型,但调用时传入的是单个字符串,PowerShell自动将其转换为仅包含1个元素的数组。
  2. 代码中直接访问$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 02:00:00