Public/Tests/Get-MsiSequence.Tests.ps1

BeforeDiscovery {
    # Windows PowerShell 5.1 has no $IsWindows variable and only runs on Windows
    $IsWindowsHost = ($PSVersionTable.PSEdition -eq 'Desktop') -or $IsWindows
    $IsWindowsElevated = $false
    if ($IsWindowsHost) {
        $principal = New-Object -TypeName System.Security.Principal.WindowsPrincipal -ArgumentList ([System.Security.Principal.WindowsIdentity]::GetCurrent())
        $IsWindowsElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
}

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue
}

Describe 'Get-MsiSequence' {
    BeforeAll {
        $FunctionPath = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Get-MsiSequence.ps1'
    }

    It 'Uses only syntax that Windows PowerShell 5.1 can parse' {
        $tokens = $null
        $errors = $null
        $null = [System.Management.Automation.Language.Parser]::ParseFile($FunctionPath, [ref]$tokens, [ref]$errors)
        $errors | Should -BeNullOrEmpty
        @($tokens | Where-Object { $_.Text -in @('??', '??=', '?.', '?[') }) | Should -BeNullOrEmpty
    }

    It 'Lets -SequenceTable be combined with the display switches' {
        $parameters = (Get-Command -Name Get-MsiSequence).Parameters
        $parameters['SequenceTable'].ParameterSets.Keys | Should -Contain 'All'
        $parameters['ShowSequences'].ParameterSets.Keys | Should -Contain 'All'
    }

    It 'Rejects a path that does not exist' {
        { Get-MsiSequence -MsiPath (Join-Path -Path $TestDrive -ChildPath 'missing.msi') } | Should -Throw '*File not found*'
    }

    It 'Rejects a file that is not an .msi' {
        $notMsi = Join-Path -Path $TestDrive -ChildPath 'setup.exe'
        Set-Content -Path $notMsi -Value 'x'
        { Get-MsiSequence -MsiPath $notMsi } | Should -Throw '*not an MSI*'
    }

    It 'Throws a clear error on non-Windows platforms' -Skip:$IsWindowsHost {
        $msi = Join-Path -Path $TestDrive -ChildPath 'package.msi'
        Set-Content -Path $msi -Value 'x'
        { Get-MsiSequence -MsiPath $msi } | Should -Throw '*requires Windows*'
    }

    It 'Writes an error, without throwing, for a file that is not a valid MSI database' -Skip:(-not $IsWindowsHost) {
        $msi = Join-Path -Path $TestDrive -ChildPath 'invalid.msi'
        Set-Content -Path $msi -Value 'this is not an MSI database'
        { Get-MsiSequence -MsiPath $msi -ShowFiles -ErrorVariable msiErrors -ErrorAction SilentlyContinue } | Should -Not -Throw
        $output = Get-MsiSequence -MsiPath $msi -ErrorVariable msiErrors -ErrorAction SilentlyContinue
        $output | Should -BeNullOrEmpty
        $msiErrors | Should -Not -BeNullOrEmpty
    }

    It 'Quotes the MSI and target paths passed to msiexec when extracting' -Skip:(-not $IsWindowsElevated) {
        $folder = Join-Path -Path $TestDrive -ChildPath 'with space'
        $null = New-Item -Path $folder -ItemType Directory -Force
        $msi = Join-Path -Path $folder -ChildPath 'my package.msi'
        Set-Content -Path $msi -Value 'x'
        $target = Join-Path -Path $folder -ChildPath 'extract here'
        $null = New-Item -Path $target -ItemType Directory -Force
        Mock -ModuleName tcs.utils Start-Process { [PSCustomObject]@{ ExitCode = 0 } }
        Get-MsiSequence -MsiPath $msi -ExtractTo $target -InformationAction SilentlyContinue
        Should -Invoke -ModuleName tcs.utils Start-Process -Times 1 -Exactly -ParameterFilter {
            ($ArgumentList -join ' ') -like '/a "*my package.msi" /qb TARGETDIR="*extract here"'
        }
    }
}

Describe 'Get-MsiSequence custom action view' {
    It 'Resets the custom action view for every MSI so an earlier, released view is never reused' {
        $functionPath = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Get-MsiSequence.ps1'
        $tokens = $null
        $errors = $null
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($functionPath, [ref]$tokens, [ref]$errors)
        $assignments = @($ast.FindAll({
                    param($node)
                    $node -is [System.Management.Automation.Language.AssignmentStatementAst] -and $node.Left.Extent.Text -eq '$caView'
                }, $true))
        $reset = @($assignments | Where-Object { $_.Right.Extent.Text -eq '$null' } | Sort-Object { $_.Extent.StartOffset })
        $open = @($assignments | Where-Object { $_.Right.Extent.Text -like '*OpenView*' })
        $reset.Count | Should -BeGreaterThan 0
        $open.Count | Should -Be 1
        $reset[0].Extent.StartOffset | Should -BeLessThan $open[0].Extent.StartOffset
    }
}