Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use C# wrapper objects for PowerShell cmdlet output #2871

Merged
merged 18 commits into from
Feb 7, 2023

Conversation

ryfu-msft
Copy link
Contributor

@ryfu-msft ryfu-msft commented Jan 19, 2023

Related to: #2796

Currently, the PowerShell module outputs a COM object which, based on feedback from the community, is not easily usable and also causes issues with certain behaviors such as getting properties and sorting by specific fields (id, version, etc.). This change fixes those issues by using a better formatted C# wrapper object around the data from the COM object and outputs that to PowerShell.

Tests:
I added basic Pester tests but did not include them in the pipeline as the module looks for the wingetdev package to be installed on the build machine (specifically executing WindowsPackageManagerServer.exe) before creating any COM instances. I will work on that in a separate PR but I have included the basic tests here which can be run locally while having the localhost webserver running. Since we already have basic E2E test coverage for the module, it is not absolutely necessary right now.

Microsoft Reviewers: Open in CodeFlow

@ryfu-msft ryfu-msft requested a review from a team as a code owner January 19, 2023 18:07
@ryfu-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

}

/// <summary>
/// Gets the source of the catalog package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe add that it's the source name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to suggested.

}

/// <summary>
/// Gets the correlation data of the install result.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this correlation data, and what format does the string have?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure, I even tried installing a package and manually failing it, but no output. I removed it entirely and if people want it back, I can add it again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correlation data is passed in from caller for their own correlation purposes. We return it so caller can correlate to their operations

/// <summary>
/// CatalogPackage wrapper object for displaying to PowerShell.
/// </summary>
public class InstalledCatalogPackage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between FoundCatalogPackage and InstalledCatalogPackage? Is it about which cmdlets they can be used in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference is whether we use InstalledVersion or DefaultInstallVersion to obtain the Version that we display to the user. I created a parent class called CatalogPackage that FoundCatalogPackage and InstalledCatalogPackage inherit from so that there less repeated properties.

src/PowerShell/tests/Setup.Tests.ps1 Outdated Show resolved Hide resolved
Comment on lines 17 to 20
$package.Name | Should -Be 'TestExeInstaller'
$package.Id | Should -Be 'AppInstallerTest.TestExeInstaller'
$package.Version | Should -Be '2.0.0.0'
$package.Source | Should -Be 'TestSource'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a way to do this all in one command. Something like

$package | Should -Be @{
  Name: 'TestExeInstaller'
  Id: 'AppInstallerTest.TestExeInstaller'
}

I have never used Pester and I don't know enough PS, so I genuinely don't know if something like that is possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this and it didn't work. Also the documentation doesn't have anything like this so I will leave it as is.

Copy link
Contributor

@Trenly Trenly Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would just need to do something like:

Function MatchesObject($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because){
 $succeeded = $true
  $failMessage = ''
  foreach ($key in $ExpectedValue.Keys){
    if ($ActualValue.Keys -contains $key){
       if ($ActualValue[$key] -ceq $ExpectedValue[$key]){
       }  else {
         $succeeded = $false
         $failMessage = "Failed to validate $key. Actual: $($ActualValue[$key]); Expected: $($ExpectedValue[$key])"
       }
    } else {
      $succeeded = $false
      $failMessage = "Failed to validate $key. Value not present in object"
    }
  }
  if ($Negate) {
    $succeeded = !$succeeded
  }
  return New-Object psobject -Property @{
    Succeeded = $succeeded
    FailureMessage = $failMessage
  }
}

Add-ShouldOperator -Name MatchObject -Test $function:MatchesObject -Alias MO

Then you could do

$package | Should -MatchObject @{ Name: 'TestExeInstaller' Id: 'AppInstallerTest.TestExeInstaller' }

https://pester.dev/docs/assertions/custom-assertions

Setup file for all Pester tests related to the Microsoft.WinGet.Client PowerShell module.
#>

BeforeAll {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine there's Pester magic that makes this run before all tests? Is it possible to add a cleanup step to always uninstall the installed tests apps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an AfterAll step that cleans up the installed test packages and sources that were added during the test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like AfterAll is executed once after all the tests. Could we want to uninstall the packages after each tests?

@ryfu-msft ryfu-msft requested a review from florelis January 19, 2023 22:44
Comment on lines 17 to 20
$package.Name | Should -Be 'TestExeInstaller'
$package.Id | Should -Be 'AppInstallerTest.TestExeInstaller'
$package.Version | Should -Be '2.0.0.0'
$package.Source | Should -Be 'TestSource'
Copy link
Contributor

@Trenly Trenly Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would just need to do something like:

Function MatchesObject($ActualValue, $ExpectedValue, [switch] $Negate, [string] $Because){
 $succeeded = $true
  $failMessage = ''
  foreach ($key in $ExpectedValue.Keys){
    if ($ActualValue.Keys -contains $key){
       if ($ActualValue[$key] -ceq $ExpectedValue[$key]){
       }  else {
         $succeeded = $false
         $failMessage = "Failed to validate $key. Actual: $($ActualValue[$key]); Expected: $($ExpectedValue[$key])"
       }
    } else {
      $succeeded = $false
      $failMessage = "Failed to validate $key. Value not present in object"
    }
  }
  if ($Negate) {
    $succeeded = !$succeeded
  }
  return New-Object psobject -Property @{
    Succeeded = $succeeded
    FailureMessage = $failMessage
  }
}

Add-ShouldOperator -Name MatchObject -Test $function:MatchesObject -Alias MO

Then you could do

$package | Should -MatchObject @{ Name: 'TestExeInstaller' Id: 'AppInstallerTest.TestExeInstaller' }

https://pester.dev/docs/assertions/custom-assertions

It 'Find by Name sort by Version' {
$package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' | Sort-Object 'Version'
$package[0].Name | Should -Be 'TestPortableExeWithCommand'
$package[1].Name | Should -Be 'TestPortableExe'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Pester set up to collect all failures? If not, I think it should be
https://pester.dev/docs/assertions/should-command#collect-all-should-failures

@microsoft microsoft deleted a comment from github-actions bot Feb 3, 2023
@microsoft microsoft deleted a comment from github-actions bot Feb 3, 2023
@ryfu-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@ryfu-msft ryfu-msft merged commit 3887bc8 into microsoft:master Feb 7, 2023
@ryfu-msft ryfu-msft deleted the powershellRichObject branch February 7, 2023 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants