Public/Tests/IntuneWin32AppManagement.Tests.ps1
|
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.intune.packaging.psd1') -Force . (Join-Path -Path $PSScriptRoot -ChildPath '../../../../tests/TestHelpers/New-TestIntuneWin.ps1') $script:Stubs = Add-GraphCommandStub } AfterAll { foreach ($name in $script:Stubs) { Remove-Item -Path "Function:\global:$name" -ErrorAction SilentlyContinue } Remove-Module -Name tcs.intune.packaging -Force -ErrorAction SilentlyContinue } Describe 'Win32 app management commands' { BeforeEach { $script:Calls = [System.Collections.Generic.List[object]]::new() $script:App = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.win32LobApp'; id = 'app-1'; displayName = 'MyApp'; notes = 'Packaged by IT' } Mock -ModuleName tcs.intune.packaging Get-MgContext { [PSCustomObject]@{ Account = 'a'; TenantId = 't' } } Mock -ModuleName tcs.intune.packaging Invoke-MgGraphRequest { $script:Calls.Add([PSCustomObject]@{ Method = $Method; Uri = $Uri; Body = $Body }) if ($Method -ne 'GET') { return } switch -Wildcard ($Uri) { '*mobileApps[?]*' { [PSCustomObject]@{ value = @($script:App, [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.winGetApp'; id = 'store'; displayName = 'MyApp' }) } } '*/relationships' { [PSCustomObject]@{ value = @() } } default { $script:App } } } } Context 'Get-IntuneWin32App' { It 'Gets an app by ID' { (Get-IntuneWin32App -Id 'app-1').id | Should -Be 'app-1' $script:Calls[0].Uri | Should -Be 'v1.0/deviceAppManagement/mobileApps/app-1' } It 'Throws for an app that is not a Win32 app' { $script:App = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.winGetApp'; id = 'store' } { Get-IntuneWin32App -Id 'store' } | Should -Throw '*not a Win32 app*' } It 'Gets only the Win32 apps with the display name' { $apps = @(Get-IntuneWin32App -Name "O'Brien") $apps.Count | Should -Be 1 $apps[0].id | Should -Be 'app-1' $script:Calls[0].Uri | Should -Be "v1.0/deviceAppManagement/mobileApps?`$filter=$([System.Uri]::EscapeDataString("displayName eq 'O''Brien'"))" } It 'Lists all Win32 apps and follows the next link' { Mock -ModuleName tcs.intune.packaging Invoke-MgGraphRequest { $script:Calls.Add([PSCustomObject]@{ Method = $Method; Uri = $Uri }) if ($Uri -like '*page2*') { [PSCustomObject]@{ value = @([PSCustomObject]@{ id = 'b' }) } } else { [PSCustomObject]@{ value = @([PSCustomObject]@{ id = 'a' }); '@odata.nextLink' = 'https://graph.microsoft.com/v1.0/page2' } } } (Get-IntuneWin32App).id | Should -Be @('a', 'b') $script:Calls[0].Uri | Should -BeLike "*isof*win32LobApp*" } It 'Throws when not connected to Microsoft Graph' { Mock -ModuleName tcs.intune.packaging Get-MgContext { } { Get-IntuneWin32App -Id 'app-1' } | Should -Throw '*Connect-MgGraph*' } } Context 'Set-IntuneWin32App' { It 'Sends only the given properties' { Set-IntuneWin32App -Id 'app-1' -InstallCommandLine 'setup.exe /S' -Publisher 'Contoso' -Confirm:$false $patch = $script:Calls | Where-Object Method -EQ 'PATCH' $patch.Uri | Should -Be 'v1.0/deviceAppManagement/mobileApps/app-1' $body = $patch.Body | ConvertFrom-Json @($body.PSObject.Properties.Name | Sort-Object) | Should -Be @('@odata.type', 'installCommandLine', 'publisher') $body.installCommandLine | Should -Be 'setup.exe /S' } It 'Appends the version to the current notes' { Set-IntuneWin32App -Id 'app-1' -Version '2.0.1' -Confirm:$false $body = ($script:Calls | Where-Object Method -EQ 'PATCH').Body | ConvertFrom-Json $body.notes | Should -Be "Packaged by IT`nVersion: 2.0.1" } It 'Updates the app from the New-IntuneApplication JSON file' { $json = Join-Path -Path $TestDrive -ChildPath 'app.json' @{ ApplicationParameters = @{ ApplicationName = 'MyApp'; Publisher = 'Contoso'; InstallCommand = 'a'; UninstallCommand = 'b'; InstallFor = 'User'; DetectionRuleConfig = @{ '@odata.type' = '#microsoft.graph.win32LobAppProductCodeRule'; ruleType = 'detection'; productCode = '{X}' } } } | ConvertTo-Json -Depth 5 | Set-Content -Path $json Set-IntuneWin32App -Id 'app-1' -JsonPath $json -Confirm:$false $body = ($script:Calls | Where-Object Method -EQ 'PATCH').Body | ConvertFrom-Json $body.displayName | Should -Be 'MyApp' $body.installExperience.runAsAccount | Should -Be 'user' @($body.rules)[0].productCode | Should -Be '{X}' } It 'Rejects rules without a detection rule' { $requirement = @{ '@odata.type' = '#microsoft.graph.win32LobAppFileSystemRule'; ruleType = 'requirement' } { Set-IntuneWin32App -Id 'app-1' -Rules $requirement -Confirm:$false } | Should -Throw '*detection rule*' } It 'Throws when there is nothing to update' { { Set-IntuneWin32App -Id 'app-1' -Confirm:$false } | Should -Throw '*Nothing to update*' } It 'Returns the app with -PassThru and sends nothing with -WhatIf' { (Set-IntuneWin32App -Id 'app-1' -Notes 'x' -PassThru -Confirm:$false).id | Should -Be 'app-1' $script:Calls.Clear() Set-IntuneWin32App -Id 'app-1' -Notes 'x' -WhatIf @($script:Calls | Where-Object Method -EQ 'PATCH').Count | Should -Be 0 } } Context 'Remove-IntuneWin32App' { It 'Deletes a Win32 app' { Remove-IntuneWin32App -Id 'app-1' -Confirm:$false ($script:Calls | Where-Object Method -EQ 'DELETE').Uri | Should -Be 'v1.0/deviceAppManagement/mobileApps/app-1' } It 'Deletes nothing with -WhatIf' { Remove-IntuneWin32App -Id 'app-1' -WhatIf @($script:Calls | Where-Object Method -EQ 'DELETE').Count | Should -Be 0 } It 'Does not delete an app that is not a Win32 app' { $script:App = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.winGetApp'; id = 'store' } Remove-IntuneWin32App -Id 'store' -Confirm:$false -ErrorVariable removeError -ErrorAction SilentlyContinue "$removeError" | Should -Match 'not a Win32 app' @($script:Calls | Where-Object Method -EQ 'DELETE').Count | Should -Be 0 } It 'Accepts apps from the pipeline' { [PSCustomObject]@{ id = 'app-1' } | Remove-IntuneWin32App -Confirm:$false @($script:Calls | Where-Object Method -EQ 'DELETE').Count | Should -Be 1 } } Context 'Supersedence and dependencies' { It 'Adds a supersedence relationship with Add-IntuneWin32AppSupersedence' { Add-IntuneWin32AppSupersedence -Id 'app-1' -SupersededAppId 'old-1' -SupersedenceType replace -Confirm:$false $post = $script:Calls | Where-Object Method -EQ 'POST' $post.Uri | Should -Be 'beta/deviceAppManagement/mobileApps/app-1/updateRelationships' $relationship = @(($post.Body | ConvertFrom-Json).relationships)[0] $relationship.'@odata.type' | Should -Be '#microsoft.graph.mobileAppSupersedence' $relationship.targetId | Should -Be 'old-1' $relationship.supersedenceType | Should -Be 'replace' } It 'Adds dependency relationships with Add-IntuneWin32AppDependency' { Add-IntuneWin32AppDependency -Id 'app-1' -DependsOnAppId 'dep-1', 'dep-2' -Confirm:$false $relationships = @((($script:Calls | Where-Object Method -EQ 'POST').Body | ConvertFrom-Json).relationships) $relationships.Count | Should -Be 2 $relationships[0].'@odata.type' | Should -Be '#microsoft.graph.mobileAppDependency' $relationships.dependencyType | Should -Be @('autoInstall', 'autoInstall') } It 'Rejects an app that supersedes or depends on itself' { { Add-IntuneWin32AppSupersedence -Id 'app-1' -SupersededAppId 'app-1' -Confirm:$false } | Should -Throw '*itself*' { Add-IntuneWin32AppDependency -Id 'app-1' -DependsOnAppId 'app-1' -Confirm:$false } | Should -Throw '*itself*' } It 'Sends nothing with -WhatIf' { Add-IntuneWin32AppSupersedence -Id 'app-1' -SupersededAppId 'old-1' -WhatIf Add-IntuneWin32AppDependency -Id 'app-1' -DependsOnAppId 'dep-1' -WhatIf @($script:Calls | Where-Object Method -EQ 'POST').Count | Should -Be 0 } } } Describe 'Get-IntuneWinPackageInfo' { It 'Returns the package metadata without the encryption keys' { $package = New-TestIntuneWin -Path (Join-Path -Path $TestDrive -ChildPath 'info.intunewin') -ContentSize 3000 -SetupFile 'install.msi' $info = $package | Get-IntuneWinPackageInfo $info.Path | Should -Be $package.FullName $info.SetupFile | Should -Be 'install.msi' $info.UnencryptedContentSize | Should -Be 2952 $info.EncryptedContentSize | Should -Be 3000 $info.ToolVersion | Should -Be '1.8.6' $info.PSObject.Properties.Name | Should -Not -Contain 'EncryptionInfo' } It 'Throws for a file that is not a .intunewin package' { $file = Join-Path -Path $TestDrive -ChildPath 'plain.txt' Set-Content -Path $file -Value 'x' { Get-IntuneWinPackageInfo -Path $file } | Should -Throw } } |