From 6af4d665cefbb6ef2d39c8f9d3f35ead411ecc61 Mon Sep 17 00:00:00 2001 From: Isaiah Williams Date: Thu, 5 Dec 2019 13:53:53 -0600 Subject: [PATCH] Authentication and performance update (#216) --- ChangeLog.md | 75 +++-- Partner-Center-PowerShell.sln | 4 +- azure-pipelines.yml | 17 +- build.proj | 2 +- build/PartnerCenter.nuspec | 2 +- docs/current-breaking-changes.md | 79 ++++++ docs/help/Connect-PartnerCenter.md | 8 +- docs/help/Get-PartnerAgreementStatus.md | 91 +++++++ docs/help/Get-PartnerAzureBillingPolicy.md | 78 ++++++ ...Get-PartnerCustomerAzurePlanEntitlement.md | 2 +- docs/help/Get-PartnerInvoiceLineItem.md | 25 +- docs/help/New-PartnerAzureSubscription.md | 26 +- docs/help/New-PartnerCustomerCart.md | 26 +- docs/help/PartnerCenter.md | 10 +- docs/help/Set-PartnerAzureBillingPolicy.md | 101 +++++++ docs/help/Set-PartnerAzureSubscription.md | 2 +- docs/help/Test-PartnerAddress.md | 35 +-- docs/upcoming-breaking-changes.md | 29 ++ .../BillingPropertyOperationsExtensions.cs | 2 +- .../LineOfCreditsOperationsExtensions.cs | 2 +- src/Billing/OperationsExtensions.cs | 2 +- .../RecipientTransfersOperationsExtensions.cs | 2 +- .../AccessTokenAuthenticator.cs | 66 ++++- .../Authenticators/AccessTokenParameters.cs | 6 - .../AuthenticationParameters.cs | 5 + .../Authenticators/DelegatingAuthenticator.cs | 16 +- .../Authenticators/DeviceCodeAuthenticator.cs | 45 ++- .../Authenticators/IAuthenticator.cs | 16 +- .../InteractiveUserAuthenticator.cs | 80 +++--- .../RefreshTokenAuthenticator.cs | 9 +- .../ServicePrincipalAuthenticator.cs | 4 +- .../Authenticators/SilentAuthenticator.cs | 7 +- .../AddPartnerCustomerCartLineItem.cs | 2 +- .../AddPartnerCustomerUserRoleMember.cs | 2 +- .../Commands/ConnectPartnerCenter.cs | 59 ++-- .../Commands/GetPartnerAgreementDetail.cs | 6 +- .../Commands/GetPartnerAgreementStatus.cs | 35 +++ .../Commands/GetPartnerAuditRecord.cs | 2 +- .../Commands/GetPartnerAzureBillingAccount.cs | 10 +- .../Commands/GetPartnerAzureBillingPolicy.cs | 42 +++ .../Commands/GetPartnerAzureBillingProfile.cs | 12 +- .../Commands/GetPartnerBillingProfile.cs | 2 +- .../Commands/GetPartnerCountryValidation.cs | 2 +- src/PowerShell/Commands/GetPartnerCustomer.cs | 74 ++--- .../Commands/GetPartnerCustomerAgreement.cs | 4 +- .../GetPartnerCustomerAzurePlanEntitlement.cs | 6 +- .../GetPartnerCustomerBillingProfile.cs | 2 +- .../Commands/GetPartnerCustomerCart.cs | 2 +- .../GetPartnerCustomerCompanyProfile.cs | 2 +- .../GetPartnerCustomerConfigurationPolicy.cs | 4 +- .../Commands/GetPartnerCustomerDevice.cs | 2 +- .../Commands/GetPartnerCustomerDeviceBatch.cs | 2 +- ...GetPartnerCustomerLicenseDeploymentInfo.cs | 2 +- .../GetPartnerCustomerManagedService.cs | 4 +- ...tnerCustomerOrderLineItemActivationLink.cs | 2 +- ...tPartnerCustomerOrderProvisioningStatus.cs | 2 +- .../GetPartnerCustomerQualification.cs | 2 +- .../GetPartnerCustomerServiceCosts.cs | 2 +- .../GetPartnerCustomerServiceCostsSummary.cs | 2 +- .../GetPartnerCustomerSubscription.cs | 70 ++--- .../GetPartnerCustomerSubscriptionAddOn.cs | 2 +- ...etPartnerCustomerSubscriptionMeterUsage.cs | 2 +- ...rCustomerSubscriptionProvisioningStatus.cs | 2 +- ...rCustomerSubscriptionRegistrationStatus.cs | 2 +- ...artnerCustomerSubscriptionResourceUsage.cs | 2 +- ...rtnerCustomerSubscriptionSupportContact.cs | 2 +- .../GetPartnerCustomerSubscriptionUpgrades.cs | 2 +- ...tPartnerCustomerSubscriptionUtilization.cs | 63 ++--- .../GetPartnerCustomerTrialConversion.cs | 2 +- .../GetPartnerCustomerUsageSummary.cs | 2 +- .../Commands/GetPartnerCustomerUser.cs | 8 +- .../Commands/GetPartnerCustomerUserLicense.cs | 18 +- .../Commands/GetPartnerCustomerUserRole.cs | 4 +- .../Commands/GetPartnerIndirectReseller.cs | 2 +- src/PowerShell/Commands/GetPartnerInvoice.cs | 6 +- .../Commands/GetPartnerInvoiceLineItem.cs | 130 +++++---- .../Commands/GetPartnerInvoiceStatement.cs | 2 +- .../Commands/GetPartnerInvoiceSummary.cs | 2 +- .../GetPartnerInvoiceTaxReceiptStatement.cs | 2 +- .../Commands/GetPartnerLegalProfile.cs | 2 +- .../GetPartnerLicenseDeploymentInfo.cs | 2 +- .../Commands/GetPartnerLicenseUsageInfo.cs | 2 +- .../Commands/GetPartnerMpnProfile.cs | 2 +- src/PowerShell/Commands/GetPartnerOffer.cs | 6 +- .../Commands/GetPartnerOfferAddon.cs | 2 +- .../Commands/GetPartnerOfferCategory.cs | 2 +- .../Commands/GetPartnerOrganizationProfile.cs | 2 +- .../Commands/GetPartnerProductAvailability.cs | 6 +- .../Commands/GetPartnerResellerRequestLink.cs | 2 +- src/PowerShell/Commands/GetPartnerRole.cs | 2 +- .../Commands/GetPartnerRoleMember.cs | 2 +- .../Commands/GetPartnerServiceIncident.cs | 2 +- .../Commands/GetPartnerServiceRequest.cs | 10 +- .../Commands/GetPartnerServiceRequestTopic.cs | 2 +- .../Commands/GetPartnerSupportProfile.cs | 2 +- src/PowerShell/Commands/GetPartnerUser.cs | 39 ++- .../Commands/GetPartnerUserSignInActivity.cs | 66 +++-- .../Commands/GetPartnerValidationCode.cs | 2 +- .../Commands/NewPartnerAccessToken.cs | 237 +++++++++------- .../Commands/NewPartnerAzureSubscription.cs | 51 +++- src/PowerShell/Commands/NewPartnerCustomer.cs | 2 +- .../NewPartnerCustomerConfigurationPolicy.cs | 3 +- .../Commands/NewPartnerCustomerDeviceBatch.cs | 4 +- src/PowerShell/Commands/PartnerAsyncCmdlet.cs | 224 +++++++++++++++ src/PowerShell/Commands/PartnerCmdlet.cs | 2 +- src/PowerShell/Commands/PartnerPSCmdlet.cs | 46 +++- ...emovePartnerCustomerConfigurationPolicy.cs | 2 +- .../Commands/RemovePartnerCustomerUser.cs | 6 +- .../RemovePartnerCustomerUserRoleMember.cs | 2 +- .../RemovePartnerResellerRelationship.cs | 2 +- .../Commands/RemovePartnerSandboxCustomer.cs | 2 +- .../Commands/RestorePartnerCustomerUser.cs | 2 +- .../Commands/SetPartnerAzureBillingProfile.cs | 65 +++++ .../Commands/SetPartnerAzureSubscription.cs | 32 ++- .../Commands/SetPartnerBillingProfile.cs | 2 +- src/PowerShell/Commands/SetPartnerCustomer.cs | 2 +- .../Commands/SetPartnerCustomerCart.cs | 2 +- .../SetPartnerCustomerConfigurationPolicy.cs | 2 +- .../SetPartnerCustomerSubscription.cs | 2 +- .../Commands/SetPartnerCustomerUser.cs | 2 +- .../Commands/SetPartnerLegalProfile.cs | 2 +- .../Commands/SetPartnerOrganizationProfile.cs | 2 +- .../Commands/SetPartnerSupportProfile.cs | 2 +- .../Commands/SubmitPartnerCustomerCart.cs | 2 +- src/PowerShell/Commands/TestPartnerAddress.cs | 19 +- .../Commands/TestPartnerDomainAvailability.cs | 2 +- .../TestPartnerSecurityRequirement.cs | 63 ++--- .../Factories/AuthenticationFactory.cs | 10 +- src/PowerShell/Factories/ClientFactory.cs | 43 ++- .../Factories/IAuthenticationFactory.cs | 4 +- src/PowerShell/Factories/IClientFactory.cs | 9 +- .../SharedTokenCacheClientFactory.cs | 44 ++- .../Models/Authentication/AuthResult.cs | 4 +- .../Models/Authentication/ModuleName.cs | 16 ++ .../PartnerAccountPropertyType.cs | 12 +- .../Models/Authentication/PartnerSession.cs | 39 +++ .../Authentication/PowerShellCredentials.cs | 6 +- .../Models/Authentication/PowerShellModule.cs | 56 ++++ .../Models/Authentication/ResourceAccount.cs | 43 +++ .../Models/Errors/PartnerExceptionRecord.cs | 1 - src/PowerShell/Models/StreamEventArgs.cs | 23 ++ .../Subscriptions/PSAzureEntitlement.cs | 49 ++++ src/PowerShell/Network/CancelRetryHandler.cs | 69 ----- .../Network/ClientTracingHandler.cs | 58 ++++ .../Network/DefaultOsBrowserWebUi.cs | 23 +- .../Network/GraphAuthenticationProvider.cs | 4 +- .../Network/RecordingTracingInterceptor.cs | 197 +++++++++++++- src/PowerShell/PartnerCenter.psd1 | 7 +- src/PowerShell/PowerShell.csproj | 8 +- .../Utilities/ConcurrencyTaskScheduler.cs | 256 ++++++++++++++++++ .../Utilities/TaskExceptionEventArgs.cs | 27 ++ .../Validations/AddressValidator.cs | 69 +++-- src/Subscription/OperationsExtensions.cs | 2 +- .../SubscriptionOperationsExtensions.cs | 2 +- .../SubscriptionsOperationsExtensions.cs | 2 +- .../TenantsOperationsExtensions.cs | 2 +- .../Factories/MockAuthenticationFactory.cs | 31 +-- .../Factories/MockClientFactory.cs | 19 +- .../Network/HttpMockHandler.cs | 5 - .../PowerShell.UnitTests.csproj | 2 +- .../Exceptions/ValidateHelpIssues.csv | 1 - tools/HelpGeneration/HelpGeneration.psm1 | 6 +- 162 files changed, 2650 insertions(+), 927 deletions(-) create mode 100644 docs/help/Get-PartnerAgreementStatus.md create mode 100644 docs/help/Get-PartnerAzureBillingPolicy.md create mode 100644 docs/help/Set-PartnerAzureBillingPolicy.md create mode 100644 src/PowerShell/Commands/GetPartnerAgreementStatus.cs create mode 100644 src/PowerShell/Commands/GetPartnerAzureBillingPolicy.cs create mode 100644 src/PowerShell/Commands/PartnerAsyncCmdlet.cs create mode 100644 src/PowerShell/Commands/SetPartnerAzureBillingProfile.cs create mode 100644 src/PowerShell/Models/Authentication/ModuleName.cs create mode 100644 src/PowerShell/Models/Authentication/PowerShellModule.cs create mode 100644 src/PowerShell/Models/Authentication/ResourceAccount.cs create mode 100644 src/PowerShell/Models/StreamEventArgs.cs create mode 100644 src/PowerShell/Models/Subscriptions/PSAzureEntitlement.cs delete mode 100644 src/PowerShell/Network/CancelRetryHandler.cs create mode 100644 src/PowerShell/Network/ClientTracingHandler.cs create mode 100644 src/PowerShell/Utilities/ConcurrencyTaskScheduler.cs create mode 100644 src/PowerShell/Utilities/TaskExceptionEventArgs.cs diff --git a/ChangeLog.md b/ChangeLog.md index c9ba9a2..1af1ef7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,7 +20,40 @@ # Change Log -## 2.0.1911.6 +## 3.0.0 - December 2019 + +* Agreement + * Added the [Get-PartnerAgreementStatus](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerAgreementStatus) command to get the status of acceptance of the Microsoft Partner Agreement for the specified partner +* Authentication + * Added the `AzureAccessToken` parameter to the [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) command, this value will be used to interact with Azure when using commands that leverage the Azure Resource Manager API + * Updated how [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) writes warnings during an authentication attempt + * Updated how [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) prompts for interaction + * When using [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) with an access token the account and tenant information are now extracted from the access token +* Azure + * Added the [Get-PartnerAzureBillingPolicy](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerAzureBillingPolicy) to get the billing policy for the specified customer + * Added the [Set-PartnerAzureBillingPolicy](https://docs.microsoft.com/powershell/module/partnercenter/Set-PartnerAzureBillingPolicy) to update the billing policy for the specified customer +* Build + * Updating the test project from .NET Core 2.2 to .NET 3.0 +* Dependency + * Updated to the latest version of the Partner Center SDK for .NET +* Invoice + * Added the `Period` parameter to the [Get-PartnerInvoiceLineItem](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerInvoiceLineItem) command to provide a way for the user to specify if they want the current or previous unbilled line items + * Addressed issue [#202](https://github.com/microsoft/Partner-Center-PowerShell/issues/202) that was returning request for invoice line items with no errors +* Module + * Addressed issue [#217](https://github.com/microsoft/Partner-Center-PowerShell/issues/217) that was impacting executing commands through Azure Automation + * Updated the transient error strategy for network operations + * When running any command with with the `Debug` parameter the request and response from the API will be written to the console in addition to any operation specific debug information +* Security + * Modified the [Get-PartnerUser](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUser) command to leverage a task scheduler for requesting from Microsoft Graph + * Modified the [Get-PartnerUserSignActivity](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUserSignActivity) command to leverage a task scheduler for requesting from Microsoft Graph + * Updated how [Test-PartnerSecurityRequirement](https://docs.microsoft.com/powershell/module/partnercenter/Test-PartnerSecurityRequirement) prompts for interaction +* Subscription + * Addressed an issue where the request for subscriptions by partner was causing an `InvalidCastException` to be thrown + * Corrected the output for the [Get-PartnerCustomerAzurePlanEntitlement](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerAzurePlanEntitlement) command +* Validation + * Addressed a scenario where a `NullReferenceException` could be thrown when running the [Test-PartnerAddress](https://docs.microsoft.com/powershell/module/partnercenter/Test-PartnerAddress) command + +## 2.0.1911.6 - November 2019 * Authentication * Updated how [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) writes warnings during an authentication attempt @@ -28,13 +61,13 @@ * Azure * Added the [Set-PartnerAzureSubscription](https://docs.microsoft.com/powershell/module/partnercenter/Set-PartnerAzureSubscription) command to update the display name of an Azure subscription provided through an Azure Plan -## 2.0.1911.5 +## 2.0.1911.5 - November 2019 * Security * Optimized the [Get-PartnerUser](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerProductUpgrade) command * Optimized the [Get-PartnerUserSignActivity](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUserSignActivity) command -## 2.0.1911.4 +## 2.0.1911.4 - November 2019 * Azure * Added the [Get-PartnerAzureBillingAccount](https://docs.microsoft.com/powershell/module/partnercenter/get-partnerazurebillingaccount) command to get billing accounts where the authenticated user has access @@ -44,19 +77,19 @@ * Updated the [Get-PartnerUser](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerProductUpgrade) command to ensure all user accounts are returned * Updated the [Get-PartnerUserSignActivity](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUserSignActivity) command to ensure all user sign-in activities are returned -## 2.0.1911.3 +## 2.0.1911.3 - November 2019 * Authentication * Addressed issue [#186](https://github.com/microsoft/Partner-Center-PowerShell/issues/186) that was preventing access token from being generated when using the device code flow * Security * Addressed issue preventing the [Test-PartnerSecurityRequirement](https://docs.microsoft.com/powershell/module/partnercenter/test-partnersecurityrequirement) command from working as expected -## 2.0.1911.2 +## 2.0.1911.2 - November 2019 * Dependencies * Addressed issues [#183](https://github.com/microsoft/Partner-Center-PowerShell/issues/181) and [#183](https://github.com/microsoft/Partner-Center-PowerShell/issues/183) caused by an assembly binding issue -## 2.0.1911.1 +## 2.0.1911.1 - November 2019 * Authentication * Addressed issue preventing CTRL+C from interrupting the waiting for a response during the interactive authentication scenario @@ -80,17 +113,17 @@ * Added the [Get-PartnerCustomerUsageRecord](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerUsageRecord) command to get month usage records for all customers * Removed the `Get-PartnerCustomerSubscriptionUsage` command due to changes with the Partner Center SDK for .NET. This command will be replaced with the [Get-PartnerCustomerSubscriptionMeterUsage](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerSubscriptionMeterUsage) and [Get-PartnerCustomerSubscriptionResourceUsage](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerSubscriptionResourceUsage) commands -## 2.0.1909.5 +## 2.0.1909.5 - September 2019 * Dependency * Corrected an issue that was preventing a dependency from being updated after a successful build -## 2.0.1909.4 +## 2.0.1909.4 - September 2019 * Authentication * Log events from the Microsoft Authentication Library (MSAL) will now be written to the console when the debug flag is set -## 2.0.1909.3 +## 2.0.1909.3 - September 2019 * Authentication * Address issue [#156](https://github.com/microsoft/Partner-Center-PowerShell/issues/156) where the refresh token was not being returned if it had not been previously used by the module during an interactive authentication attempt @@ -98,12 +131,12 @@ * Security * Adding the [Test-PartnerSecurityRequirement](https://docs.microsoft.com/powershell/module/partnercenter/Test-PartnerSecurityRequirement) command to help validate that the authenticating account was challenged for multi-factor authentication -## 2.0.1909.2 +## 2.0.1909.2 - September 2019 * Authentication * Addressed issue [#153](https://github.com/microsoft/Partner-Center-PowerShell/issues/153) that was preventing the [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) command from working as expected. -## 2.0.1909.1 +## 2.0.1909.1 - September 2019 * Agreements * Added the [Get-PartnerAgreementTemplate](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerAgreementTemplate) command to provide access to the links download or view the Microsoft Customer Agreement @@ -119,7 +152,7 @@ * Subscriptions * Added the [New-PartnerCustomerSubscriptionActivation](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerSubscriptionActivation) command to make it where third-party subscriptions can be activated in the integration sandbox -## 1.5.1908.1 +## 1.5.1908.1 - August 2019 * Authentication * Transitioned from Active Directory Authentication Library (ADAL) to the Microsoft Authentication Library (MSAL) @@ -127,33 +160,33 @@ * Added the [Get-PartnerRole](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerRole) command to get partner roles * Added the [Get-PartnerRoleMember](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerRoleMember) command to get the members for the specified partner role -## 1.5.1907.2 +## 1.5.1907.2 - July 2019 * Devices * Modified the output for the [New-PartnerCustomerDeviceBatch](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerCustomerAgreement) command. -## 1.5.1907.1 +## 1.5.1907.1 - July 2019 * Agreements * Removed the *UserId* parameter from the [New-PartnerCustomerAgreement](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerCustomerAgreement) command * Devices * Addressed an issue preventing the successful creation of a device batch -## 1.5.1905.1 +## 1.5.1905.1 - May 2019 * Users * Added the following properties to the user model * ImmutableId * PhoneNumber -## 1.5.1904.3 +## 1.5.1904.3 - April 2019 * Invoices * Addressed issue [#117](https://github.com/Microsoft/Partner-Center-PowerShell/issues/117), where the cannot access stream error was being thrown * Subscriptions * Added breaking change warning for the removal the *AutoRenew* flag from the [Set-PartnerCustomerSubscription](https://docs.microsoft.com/powershell/module/partnercenter/set-partnercustomersubscription) command -## 1.5.1904.2 +## 1.5.1904.2 - April 2019 * Authentication * Addressed issue [#113](https://github.com/Microsoft/Partner-Center-PowerShell/issues/113), where the access token would expire for long running operations @@ -162,7 +195,7 @@ * Utilization * Added the page size parameter to the Get-PartnerCustomerSubscriptionUtilization command -## 1.5.1904.1 +## 1.5.1904.1 - April 2019 * Auditing * Renamed the CreateInvoice operation type to ReadyInvoice @@ -173,12 +206,12 @@ * Utilization * Modified the default end date value for the Get-PartnerCustomerSubscriptionUtilization command to use UTC time -## 1.5.1903.6 +## 1.5.1903.6 - March 2019 * Products * Addressed an issue with requesting products -## 1.5.1903.5 +## 1.5.1903.5 - March 2019 * Auditing * Added new operation and resource types @@ -238,7 +271,7 @@ * Validations * Added the ability to request validation codes used to create Government Community Cloud customers -## Version 1.5.1902.5 +## Version 1.5.1902.5 - February 2019 * Added the New-PartnerCustomerApplicationConsent command * This command can be used to create a new application consent for the specified customer diff --git a/Partner-Center-PowerShell.sln b/Partner-Center-PowerShell.sln index 3f084d2..6004bb1 100644 --- a/Partner-Center-PowerShell.sln +++ b/Partner-Center-PowerShell.sln @@ -20,9 +20,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BE2D140E-5 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShell.UnitTests", "test\PowerShell.UnitTests\PowerShell.UnitTests.csproj", "{08C08BAF-107D-43BB-9560-DA2D40B27816}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing", "src\Billing\Billing.csproj", "{C445D44E-EEB2-4B49-A862-0F0E292F9843}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Billing", "src\Billing\Billing.csproj", "{C445D44E-EEB2-4B49-A862-0F0E292F9843}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Subscription", "src\Subscription\Subscription.csproj", "{74A40D93-A16E-4823-A8A9-25EDFDF9886A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Subscription", "src\Subscription\Subscription.csproj", "{74A40D93-A16E-4823-A8A9-25EDFDF9886A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5b9bfbd..d31e3ec 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,11 +1,11 @@ strategy: matrix: Linux: - imageName: 'ubuntu-16.04' + imageName: 'ubuntu-latest' macOS: - imageName: 'macos-10.13' + imageName: 'macOS-latest' Windows: - imageName: 'Windows-2019' + imageName: 'windows-latest' trigger: - master @@ -31,7 +31,14 @@ steps: targetArgument: '$(Build.SourcesDirectory)' result: 'PoliCheck.xml' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - + +- task: UseDotNet@2 + displayName: 'Use .NET Core 3.0' + inputs: + packageType: sdk + version: 3.0.x + installationPath: $(Agent.ToolsDirectory)/dotnet + - task: DotNetCoreCLI@2 displayName: Build inputs: @@ -118,7 +125,7 @@ steps: - task: DeleteFiles@1 displayName: Delete the code sign summary file inputs: - SourceFolder: '_layout' + SourceFolder: '$(system.DefaultWorkingDirectory)' Contents: '**\CodeSignSummary-*.md' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) diff --git a/build.proj b/build.proj index 7e82fa6..8465e9c 100644 --- a/build.proj +++ b/build.proj @@ -68,7 +68,7 @@ - + diff --git a/build/PartnerCenter.nuspec b/build/PartnerCenter.nuspec index 0ef3d63..12256a1 100644 --- a/build/PartnerCenter.nuspec +++ b/build/PartnerCenter.nuspec @@ -2,7 +2,7 @@ PartnerCenter - 2.0.1911.6 + 3.0.0 Microsoft Corporation Microsoft false diff --git a/docs/current-breaking-changes.md b/docs/current-breaking-changes.md index e69de29..79c68b9 100644 --- a/docs/current-breaking-changes.md +++ b/docs/current-breaking-changes.md @@ -0,0 +1,79 @@ + + +# Current Breaking Changes + +## Release 3.0.0 - December 2019 + +* Subscription + * [Get-PartnerCustomerAzurePlanEntitlement](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerAzurePlanEntitlement) output has changed + + ```output + # Old + ContinuationToken : + Items : {9681cddd-4b96-4d67-96e5-399a827d5375} + TotalCount : 1 + Links : Microsoft.Store.PartnerCenter.Models.StandardResourceCollectionLinks + Attributes : Microsoft.Store.PartnerCenter.Models.ResourceAttributes + + # New + FriendlyName Id Status SubscriptionId + ------------ -- ------ -------------- + Microsoft Azure 9681cddd-4b96-4d67-96e5-399a827d5375 active 0d066578-66b7-40f6-afad-6e179df3ad80 + ``` + + * [New-PartnerAzureSubscription](https://docs.microsoft.com/powershell/module/partnercenter/Neew-PartnerAzureSubscription) the `CustomerName` parameter will be replaced by the `CustomerId` parameter starting wth version 3.0.1 + + ```powershell + # Old + New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerName 'Contoso' -DisplayName 'Microsoft Azure' + + # New + New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '1e5a6ab0-e5ef-4f4e-a208-399e792b5ed4' -DisplayName 'Microsoft Azure' + ``` + +## Release 2.0.1910.1 - October 2019 + +* Usage + * Removed the `Get-PartnerCustomerSubscriptionUsage` command due to changes with the Partner Center SDK for .NET. This command will be replaced with the [Get-PartnerCustomerSubscriptionMeterUsage](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerSubscriptionMeterUsage) and [Get-PartnerCustomerSubscriptionResourceUsage](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerSubscriptionResourceUsage) commands + +## Release 2.0.1909.1 - September 2019 + +* Authentication + * Environments have been renamed to match the Azure PowerShell module. The new values are: *AzureChinaCloud*, *AzureCloud*, *AzureGermanCloud*, *AzurePPE*, and *AzureUSGovernment* + * Replaced the `Consent` parameter with the `UseAuthorizationCode` parameter for the `New-PartnerAccessToken` cmdlet + * Replaced the `Resource` parameter with the `Scopes` parameter for the `Connect-PartnerCenter` and `New-PartnerAccessToken` cmdlets + * ServicePrincipal parameter is now required when using a confidential client with the `Connect-PartnerCenter` and `New-PartnerAccessToken` cmdlets + * When using the [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/new-partneraccesstoken) command and the `UseAuthorizationCode` parameter you will be prompted to authentication interactively using the authorization code flow. The redirect URI value will generated dynamically. This generation process will attempt to find a port between 8400 and 8999 that is not in use. Once an available port has been found, the redirect URL value will be constructed (e.g. ). So, it is important that you have configured the redirect URI value for your Azure Active Directory application accordingly. +* Module + * The `PartnerCenter` module now supports PowerShell 5.1 and PowerShell, as a result the `PartnerCenter.NetCore` module will be retired + +## Release 1.5.1906.1 - June 2019 + +* Agreements + * The *UserId* parameter will be removed from the [New-PartnerCustomerAgreement](https://docs.microsoft.com/powershell/module/partnercenter/new-partnercustomeragreement) command. Enhancements to the API have been to derive this value based on the authenticated user + +## Release 1.5.1905.1 - May 2019 + +* Subscriptions + * The *AutoRenew* flag will be removed from the [Set-PartnerCustomerSubscription](https://docs.microsoft.com/powershell/module/partnercenter/set-partnercustomersubscription) command diff --git a/docs/help/Connect-PartnerCenter.md b/docs/help/Connect-PartnerCenter.md index 8e80a0e..18e6393 100644 --- a/docs/help/Connect-PartnerCenter.md +++ b/docs/help/Connect-PartnerCenter.md @@ -68,16 +68,14 @@ PS C:\> Connect-PartnerCenter -Credential $credential -Tenant 'xxxx-xxxx-xxxx-xx The first command gets the service principal credentials (application identifier and service principal secret), and then stores them in the $credential variable. The second command connects to Partner Center using the service principal credentials stored in $credential for the specified Tenant. The ServicePrincipal switch parameter indicates that the account authenticates as a service principal. -### Example 3: Connect to Partner using an access token +### Example 3: Connect to Partner using a refresh token ```powershell -PS C:\> $credential = Get-Credential PS C:\> $refreshToken = '' -PS C:\> $token = New-PartnerAccessToken -ApplicationId 'xxxx-xxxx-xxxx-xxxx' -Credential $credential -RefreshToken $refreshToken -Scopes "https://api.partnercenter.microsoft.com/user_impersonation" -Tenant 'xxxx-xxxx-xxxx-xxxx' -PS C:\> Connect-PartnerCenter -AccessToken $token.AccessToken +PS C:\> Connect-PartnerCenter -ApplicationId 'xxxx-xxxx-xxxx-xxxx' -RefreshToken $refreshToken ``` -The first command gets the service principal credentials (application identifier and service principal secret), and then stores them in the $credential variable. The third command generates a new access token using the specified refresh token. The final command connects to Partner Center using the access token stored in $token.AccessToken for authentication. The ServicePrincipal switch parameter indicates that the refresh token was generated using a confidential client. +Connects to Partner Center using a refresh token. ## PARAMETERS diff --git a/docs/help/Get-PartnerAgreementStatus.md b/docs/help/Get-PartnerAgreementStatus.md new file mode 100644 index 0000000..d7f2979 --- /dev/null +++ b/docs/help/Get-PartnerAgreementStatus.md @@ -0,0 +1,91 @@ +--- +content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Get-PartnerAgreementStatus.md +external help file: Microsoft.Store.PartnerCenter.PowerShell.dll-Help.xml +Module Name: PartnerCenter +online version: https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerAgreementStatus +original_content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Get-PartnerAgreementStatus.md +schema: 2.0.0 +--- + +# Get-PartnerAgreementStatus + +## SYNOPSIS +Gets the status of acceptance of the Microsoft Partner Agreement for the specified partner. + +## SYNTAX + +### ByTenantId (Default) +```powershell +Get-PartnerAgreementStatus [-TenantId] [] +``` + +### ByMpnId +```powershell +Get-PartnerAgreementStatus [-MpnId] [] +``` + +## DESCRIPTION +Gets the status of acceptance of the Microsoft Partner Agreement for the specified partner. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Get-PartnerAgreementStatus -MpnId '999999' +``` + +Gets the status of acceptance of the Microsoft Partner Agreement for the specified partner. + +### Example 2 +```powershell +PS C:\> Get-PartnerAgreementStatus -TenantId 'd96a841d-1672-4175-a878-df65b98a8550' +``` + +Gets the status of acceptance of the Microsoft Partner Agreement for the specified partner. + +## PARAMETERS + +### -MpnId +The Microsoft Partner Network (MPN) identifier for the partner. + +```yaml +Type: String +Parameter Sets: ByMpnId +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TenantId +The tenant identifier for the partner. + +```yaml +Type: String +Parameter Sets: ByTenantId +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### Microsoft.Store.PartnerCenter.Models.Compliance.AgreementSignatureStatus + +## NOTES + +## RELATED LINKS diff --git a/docs/help/Get-PartnerAzureBillingPolicy.md b/docs/help/Get-PartnerAzureBillingPolicy.md new file mode 100644 index 0000000..4d73996 --- /dev/null +++ b/docs/help/Get-PartnerAzureBillingPolicy.md @@ -0,0 +1,78 @@ +--- +content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Get-PartnerAzureBillingPolicy.md +external help file: Microsoft.Store.PartnerCenter.PowerShell.dll-Help.xml +Module Name: PartnerCenter +online version: https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerAzureBillingPolicy +original_content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Get-PartnerAzureBillingPolicy.md +schema: 2.0.0 +--- + +# Get-PartnerAzureBillingPolicy + +## SYNOPSIS +Gets the billing policy for the specified customer. + +## SYNTAX + +```powershell +Get-PartnerAzureBillingPolicy -BillingAccountName -CustomerId [] +``` + +## DESCRIPTION +Gets the billing policy for the specified customer. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Get-PartnerAzureBillingPolicy -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '7b93c1be-57f6-4d8c-9270-e9b97c071557' +``` + +Gets the billing policy for the specified customer. + +## PARAMETERS + +### -BillingAccountName +The name for the billing account. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CustomerId +The identifier for the customer. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### Microsoft.Azure.Management.Billing.Models.CustomerPolicy + +## NOTES + +## RELATED LINKS diff --git a/docs/help/Get-PartnerCustomerAzurePlanEntitlement.md b/docs/help/Get-PartnerCustomerAzurePlanEntitlement.md index 6dd1a63..1a8047c 100644 --- a/docs/help/Get-PartnerCustomerAzurePlanEntitlement.md +++ b/docs/help/Get-PartnerCustomerAzurePlanEntitlement.md @@ -71,7 +71,7 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## OUTPUTS -### Microsoft.Store.PartnerCenter.Models.Subscriptions.AzureEntitlement +### Microsoft.Store.PartnerCenter.PowerShell.Models.Subscriptions.PSAzureEntitlement ## NOTES diff --git a/docs/help/Get-PartnerInvoiceLineItem.md b/docs/help/Get-PartnerInvoiceLineItem.md index 28fb72b..4e9fbf8 100644 --- a/docs/help/Get-PartnerInvoiceLineItem.md +++ b/docs/help/Get-PartnerInvoiceLineItem.md @@ -14,11 +14,18 @@ Gets the line items for the specified invoice. ## SYNTAX +### ByInvoice (Default) ```powershell Get-PartnerInvoiceLineItem -BillingProvider [-CurrencyCode ] -InvoiceId -LineItemType [] ``` +### ByBillingPeriod +```powershell +Get-PartnerInvoiceLineItem -BillingProvider [-CurrencyCode ] -InvoiceId + -LineItemType -Period [] +``` + ## DESCRIPTION Gets the line items for the specified invoice. @@ -40,7 +47,7 @@ The billing provide for the line items. Type: BillingProvider Parameter Sets: (All) Aliases: -Accepted values: Azure, Office, OneTime, Marketplace +Accepted values: All, Azure, Office, OneTime, Marketplace Required: True Position: Named @@ -95,6 +102,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Period +The billing period for the line items. + +```yaml +Type: BillingPeriod +Parameter Sets: ByBillingPeriod +Aliases: +Accepted values: Current, Previous + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/docs/help/New-PartnerAzureSubscription.md b/docs/help/New-PartnerAzureSubscription.md index 91f1532..c085763 100644 --- a/docs/help/New-PartnerAzureSubscription.md +++ b/docs/help/New-PartnerAzureSubscription.md @@ -14,11 +14,18 @@ Creates a new Azure subscription for Microsoft Partner Agreement billing account ## SYNTAX +### ByCustomerName (Default) ```powershell New-PartnerAzureSubscription -BillingAccountName -CustomerName -DisplayName [-ResellerId ] [] ``` +### ByCustomerId +```powershell +New-PartnerAzureSubscription -BillingAccountName -CustomerId -DisplayName + [-ResellerId ] [] +``` + ## DESCRIPTION Creates a new Azure subscription for Microsoft Partner Agreement billing account. @@ -26,7 +33,7 @@ Creates a new Azure subscription for Microsoft Partner Agreement billing account ### Example 1 ```powershell -PS C:\> New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerName 'Contoso' -DisplayName 'Microsoft Azure' +PS C:\> New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId 'cb20b9f1-d3e8-4dad-9d4f-5e4c92baed92' -DisplayName 'Microsoft Azure' ``` Creates a new Azure subscription for Microsoft Partner Agreement billing account. @@ -48,12 +55,27 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -CustomerId +The identifier for the customer. + +```yaml +Type: String +Parameter Sets: ByCustomerId +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -CustomerName The name for the customer. ```yaml Type: String -Parameter Sets: (All) +Parameter Sets: ByCustomerName Aliases: Required: True diff --git a/docs/help/New-PartnerCustomerCart.md b/docs/help/New-PartnerCustomerCart.md index 80347b9..f01620a 100644 --- a/docs/help/New-PartnerCustomerCart.md +++ b/docs/help/New-PartnerCustomerCart.md @@ -10,7 +10,7 @@ schema: 2.0.0 # New-PartnerCustomerCart ## SYNOPSIS -Creates an order for a customer. +Creates a cart for a customer. ## SYNTAX @@ -20,12 +20,32 @@ New-PartnerCustomerCart -CustomerId -LineItems [-Wha ``` ## DESCRIPTION -Creates an order for a customer. +Creates a cart for a customer. ## EXAMPLES ### Example 1 ```powershell +PS C:\> # Get the product information for the Azure Plan +PS C:\> $product = Get-PartnerProduct -ProductId 'DZH318Z0BPS6' +PS C:\> # Get the SKU information for the Azure Plan +PS C:\> $sku = Get-PartnerProductSku -ProductId $product.ProductId +PS C:\> # Get the availability information required for purchasing an Azure Plan +PS C:\> $availability = Get-PartnerProductAvailability -ProductId $product.ProductId -SkuId $sku.SkuId +PS C:\> +PS C:\> $lineItem = New-Object -TypeName Microsoft.Store.PartnerCenter.PowerShell.Models.Carts.PSCartLineItem +PS C:\> +PS C:\> $lineItem.BillingCycle = 'OneTime' +PS C:\> $lineItem.CatalogItemId = $availability.CatalogItemId +PS C:\> $lineItem.Quantity = 1 +PS C:\> +PS C:\> New-PartnerCustomerCart -CustomerId '46a62ece-10ad-42e5-b3f1-b2ed53e6fc08' -LineItems $lineItem +``` + +Creates a cart for the specified with a line item to purchase an Azure Plan + +### Example 2 +```powershell PS C:\> $lineItem = New-Object -TypeName Microsoft.Store.PartnerCenter.PowerShell.Models.Carts.PSCartLineItem PS C:\> PS C:\> $lineItem.BillingCycle = 'OneTime' @@ -39,7 +59,7 @@ PS C:\> PS C:\> New-PartnerCustomerCart -CustomerId '46a62ece-10ad-42e5-b3f1-b2ed53e6fc08' -LineItems $lineItem ``` -Creates an order for a customer. +Creates an cart for a customer. ## PARAMETERS diff --git a/docs/help/PartnerCenter.md b/docs/help/PartnerCenter.md index ee7e830..3f1e064 100644 --- a/docs/help/PartnerCenter.md +++ b/docs/help/PartnerCenter.md @@ -29,12 +29,18 @@ Gets the agreement metadata for the Microsoft Cloud Agreement. ### [Get-PartnerAgreementDocument](Get-PartnerAgreementDocument.md) Gets the links to download or view the Microsoft Customer Agreement template. +### [Get-PartnerAgreementStatus](Get-PartnerAgreementStatus.md) +Gets the status of acceptance of the Microsoft Partner Agreement for the specified partner. + ### [Get-PartnerAuditRecord](Get-PartnerAuditRecord.md) Gets audit records from Partner Center. ### [Get-PartnerAzureBillingAccount](Get-PartnerAzureBillingAccount.md) Gets the billing accounts where the authenticated user has access. +### [Get-PartnerAzureBillingPolicy](Get-PartnerAzureBillingPolicy.md) +Gets the billing policy for the specified customer. + ### [Get-PartnerAzureBillingProfile](Get-PartnerAzureBillingProfile.md) Gets the billing profiles for specified billing account. @@ -308,6 +314,9 @@ Display detailed information about PowerShell errors, with extended details for ### [Restore-PartnerCustomerUser](Restore-PartnerCustomerUser.md) Restores a previously removed customer user from the customer's tenant. +### [Set-PartnerAzureBillingPolicy](Set-PartnerAzureBillingPolicy.md) +Updates the billing policy for the specified customer. + ### [Set-PartnerAzureSubscription](Set-PartnerAzureSubscription.md) Updates an Azure subscription that is part of an Azure Plan. @@ -361,4 +370,3 @@ Tests if the specified domain name is available for creating a new tenant. ### [Test-PartnerSecurityRequirement](Test-PartnerSecurityRequirement.md) Tests the account, used during authentication, if multi-factor authentication was enforced. - diff --git a/docs/help/Set-PartnerAzureBillingPolicy.md b/docs/help/Set-PartnerAzureBillingPolicy.md new file mode 100644 index 0000000..86ec54c --- /dev/null +++ b/docs/help/Set-PartnerAzureBillingPolicy.md @@ -0,0 +1,101 @@ +--- +content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Set-PartnerAzureBillingPolicy.md +external help file: Microsoft.Store.PartnerCenter.PowerShell.dll-Help.xml +Module Name: PartnerCenter +online version: https://docs.microsoft.com/powershell/module/partnercenter/Set-PartnerAzureBillingPolicy +original_content_git_url: https://github.com/Microsoft/Partner-Center-PowerShell/blob/master/docs/help/Set-PartnerAzureBillingPolicy.md +schema: 2.0.0 +--- + +# Set-PartnerAzureBillingPolicy + +## SYNOPSIS +Updates the billing policy for the specified customer. + +## SYNTAX + +```powershell +Set-PartnerAzureBillingPolicy -BillingAccountName -CustomerId [-ViewCharges] + [] +``` + +## DESCRIPTION +Updates the billing policy for the specified customer. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Set-PartnerAzureBillingPolicy -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '7b93c1be-57f6-4d8c-9270-e9b97c071557' -ViewCharges:$true +``` + +Enables the view charges feature which allows the customer to see retail pricing for Azure services. + +### Example 2 +```powershell +PS C:\> Set-PartnerAzureBillingPolicy -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '7b93c1be-57f6-4d8c-9270-e9b97c071557' -ViewCharges:$false +``` + +Disables the view charges feature which allows the customer to see retail pricing for Azure services. + +## PARAMETERS + +### -BillingAccountName +The name for the billing account. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CustomerId +The identifier for the customer. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ViewCharges +A flag that indicates whether or not the customer can view charges for Azure services. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### Microsoft.Azure.Management.Billing.Models.CustomerPolicy + +## NOTES + +## RELATED LINKS diff --git a/docs/help/Set-PartnerAzureSubscription.md b/docs/help/Set-PartnerAzureSubscription.md index a399dd2..6e79720 100644 --- a/docs/help/Set-PartnerAzureSubscription.md +++ b/docs/help/Set-PartnerAzureSubscription.md @@ -64,7 +64,7 @@ Accept wildcard characters: False ``` ### -SubscriptionName -The display name for the subscription.. +The display name for the subscription. ```yaml Type: String diff --git a/docs/help/Test-PartnerAddress.md b/docs/help/Test-PartnerAddress.md index 314ae44..1f63300 100644 --- a/docs/help/Test-PartnerAddress.md +++ b/docs/help/Test-PartnerAddress.md @@ -16,7 +16,7 @@ Tests whether or not the specified address is valid. ```powershell Test-PartnerAddress -AddressLine1 [-AddressLine2 ] [-City ] [-Country ] - -PostalCode [-Region ] [-State ] [-WhatIf] [-Confirm] [] + [-PostalCode ] [-Region ] [-State ] [] ``` ## DESCRIPTION @@ -101,7 +101,7 @@ Type: String Parameter Sets: (All) Aliases: -Required: True +Required: False Position: Named Default value: None Accept pipeline input: False @@ -138,37 +138,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/docs/upcoming-breaking-changes.md b/docs/upcoming-breaking-changes.md index 4640890..7ab445e 100644 --- a/docs/upcoming-breaking-changes.md +++ b/docs/upcoming-breaking-changes.md @@ -23,6 +23,35 @@ # Upcoming Breaking Changes +## Release 3.0.0 - December 2019 + +* Subscription + * [Get-PartnerCustomerAzurePlanEntitlement](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerCustomerAzurePlanEntitlement) output has changed + + ```output + # Old + ContinuationToken : + Items : {9681cddd-4b96-4d67-96e5-399a827d5375} + TotalCount : 1 + Links : Microsoft.Store.PartnerCenter.Models.StandardResourceCollectionLinks + Attributes : Microsoft.Store.PartnerCenter.Models.ResourceAttributes + + # New + FriendlyName Id Status SubscriptionId + ------------ -- ------ -------------- + Microsoft Azure 9681cddd-4b96-4d67-96e5-399a827d5375 active 0d066578-66b7-40f6-afad-6e179df3ad80 + ``` + + * [New-PartnerAzureSubscription](https://docs.microsoft.com/powershell/module/partnercenter/Neew-PartnerAzureSubscription) the `CustomerName` parameter will be replaced by the `CustomerId` parameter starting wth version 3.0.1 + + ```powershell + # Old + New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerName 'Contoso' -DisplayName 'Microsoft Azure' + + # New + New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '1e5a6ab0-e5ef-4f4e-a208-399e792b5ed4' -DisplayName 'Microsoft Azure' + ``` + ## Release 2.0.1910.1 - October 2019 * Usage diff --git a/src/Billing/BillingPropertyOperationsExtensions.cs b/src/Billing/BillingPropertyOperationsExtensions.cs index 4c39b8e..72a3e83 100644 --- a/src/Billing/BillingPropertyOperationsExtensions.cs +++ b/src/Billing/BillingPropertyOperationsExtensions.cs @@ -28,7 +28,7 @@ namespace Microsoft.Azure.Management.Billing /// public static BillingProperty Get(this IBillingPropertyOperations operations) { - return operations.GetAsync().GetAwaiter().GetResult(); + return operations.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Billing/LineOfCreditsOperationsExtensions.cs b/src/Billing/LineOfCreditsOperationsExtensions.cs index 28f1ef8..99189a4 100644 --- a/src/Billing/LineOfCreditsOperationsExtensions.cs +++ b/src/Billing/LineOfCreditsOperationsExtensions.cs @@ -27,7 +27,7 @@ namespace Microsoft.Azure.Management.Billing /// public static LineOfCredit Get(this ILineOfCreditsOperations operations) { - return operations.GetAsync().GetAwaiter().GetResult(); + return operations.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Billing/OperationsExtensions.cs b/src/Billing/OperationsExtensions.cs index 0af6ff6..ef532f3 100644 --- a/src/Billing/OperationsExtensions.cs +++ b/src/Billing/OperationsExtensions.cs @@ -28,7 +28,7 @@ namespace Microsoft.Azure.Management.Billing /// public static IPage List(this IOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Billing/RecipientTransfersOperationsExtensions.cs b/src/Billing/RecipientTransfersOperationsExtensions.cs index dcdcc00..dc1dfe6 100644 --- a/src/Billing/RecipientTransfersOperationsExtensions.cs +++ b/src/Billing/RecipientTransfersOperationsExtensions.cs @@ -178,7 +178,7 @@ namespace Microsoft.Azure.Management.Billing /// public static IPage List(this IRecipientTransfersOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/PowerShell/Authenticators/AccessTokenAuthenticator.cs b/src/PowerShell/Authenticators/AccessTokenAuthenticator.cs index b55cf51..d1adb5f 100644 --- a/src/PowerShell/Authenticators/AccessTokenAuthenticator.cs +++ b/src/PowerShell/Authenticators/AccessTokenAuthenticator.cs @@ -4,11 +4,17 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { using System; + using System.Linq; + using System.Management.Automation; + using System.Security.Claims; using System.Threading; using System.Threading.Tasks; + using Extensions; using Identity.Client; using IdentityModel.JsonWebTokens; + using Models.Authentication; using PartnerCenter.Exceptions; + using Rest; /// /// Provides the ability to authenticate using an access token. @@ -19,33 +25,48 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction = null, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { - AccessTokenParameters accessTokenParameters = parameters as AccessTokenParameters; - JsonWebToken jwt = new JsonWebToken(accessTokenParameters.AccessToken); + JsonWebToken token; + string value; - if (DateTimeOffset.UtcNow > jwt.ValidTo) + if (parameters.Scopes.Contains($"{parameters.Environment.PartnerCenterEndpoint}/user_impersonation")) + { + value = parameters.Account.GetProperty(PartnerAccountPropertyType.AccessToken); + } + else + { + throw new PSInvalidOperationException("This operation is not supported when you connect using an access token. Please connect interactively or using a refresh token."); + } + + token = new JsonWebToken(value); + + ServiceClientTracing.Information($"[AccessTokenAuthenticator] The specified access token expires at {token.ValidTo}"); + + if (DateTimeOffset.UtcNow > token.ValidTo) { throw new PartnerException("The access token has expired. Generate a new one and try again."); } await Task.CompletedTask; + ServiceClientTracing.Information("[AccessTokenAuthenticator] Constructing the authentication result based on the specified access token"); + return new AuthenticationResult( - accessTokenParameters.AccessToken, + value, false, null, - jwt.ValidTo, - jwt.ValidTo, - parameters.Account.Tenant, + token.ValidTo, + token.ValidTo, + token.GetClaim("tid").Value, + GetAccount(token), null, - null, - parameters.Scopes); + parameters.Scopes, + Guid.Empty); } /// @@ -57,5 +78,28 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { return parameters is AccessTokenParameters; } + + private IAccount GetAccount(JsonWebToken token) + { + token.AssertNotNull(nameof(token)); + + token.TryGetClaim("upn", out Claim claim); + + if (claim != null) + { + ServiceClientTracing.Information($"[AccessTokenAuthenticator] The UPN claim value is {claim.Value}"); + ServiceClientTracing.Information($"[AccessTokenAuthenticator] Constructing the resource account value based on specified access token"); + + return new ResourceAccount( + "login.microsoftonline.com", + $"{token.GetClaim("oid").Value}.{token.GetClaim("tid")}", + token.GetClaim("oid").Value, + token.GetClaim("tid").Value, + claim.Value); + } + + ServiceClientTracing.Information("[AccessTokenAuthenticator] The UPN claim is not present in the access token."); + return null; + } } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/AccessTokenParameters.cs b/src/PowerShell/Authenticators/AccessTokenParameters.cs index 00c0b6d..5d948ed 100644 --- a/src/PowerShell/Authenticators/AccessTokenParameters.cs +++ b/src/PowerShell/Authenticators/AccessTokenParameters.cs @@ -4,7 +4,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { using System.Collections.Generic; - using Extensions; using Models.Authentication; /// @@ -19,10 +18,5 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators : base(account, environment, scopes) { } - - /// - /// Gets the access token. - /// - public string AccessToken => Account.GetProperty(PartnerAccountPropertyType.AccessToken); } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/AuthenticationParameters.cs b/src/PowerShell/Authenticators/AuthenticationParameters.cs index eef1640..42d8fa4 100644 --- a/src/PowerShell/Authenticators/AuthenticationParameters.cs +++ b/src/PowerShell/Authenticators/AuthenticationParameters.cs @@ -4,6 +4,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { using System.Collections.Generic; + using Extensions; using Models.Authentication; /// @@ -16,6 +17,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// protected AuthenticationParameters(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes) { + account.AssertNotNull(nameof(account)); + environment.AssertNotNull(nameof(environment)); + scopes.AssertNotNull(nameof(scopes)); + Account = account; Environment = environment; Scopes = scopes; diff --git a/src/PowerShell/Authenticators/DelegatingAuthenticator.cs b/src/PowerShell/Authenticators/DelegatingAuthenticator.cs index 2f5ab0f..d5aed37 100644 --- a/src/PowerShell/Authenticators/DelegatingAuthenticator.cs +++ b/src/PowerShell/Authenticators/DelegatingAuthenticator.cs @@ -3,7 +3,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { - using System; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -26,12 +25,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public abstract Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction, CancellationToken cancellationToken = default); + public abstract Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default); /// /// Determine if this authenticator can apply to the given authentication parameters. @@ -129,25 +127,21 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// /// The complex object containing authentication specific information. /// The token based authentication information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// true if the request can be authenticated; otherwise false. - public bool TryAuthenticate(AuthenticationParameters parameters, out Task token, Action promptAction = null, CancellationToken cancellationToken = default) + public async Task TryAuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { - token = null; - if (CanAuthenticate(parameters)) { - token = AuthenticateAsync(parameters, promptAction, cancellationToken); - return true; + return await AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false); } if (Next != null) { - return Next.TryAuthenticate(parameters, out token, promptAction, cancellationToken); + return await Next.TryAuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false); } - return false; + return null; } } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/DeviceCodeAuthenticator.cs b/src/PowerShell/Authenticators/DeviceCodeAuthenticator.cs index b1b52e8..4d4bfd5 100644 --- a/src/PowerShell/Authenticators/DeviceCodeAuthenticator.cs +++ b/src/PowerShell/Authenticators/DeviceCodeAuthenticator.cs @@ -8,52 +8,33 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators using System.Threading.Tasks; using Extensions; using Identity.Client; + using Models; + using Models.Authentication; + using Rest; /// /// Provides the ability to authenticate using the device code flow. /// internal class DeviceCodeAuthenticator : DelegatingAuthenticator { - /// - /// The message that will be written utilizing the prompt action. - /// - private string message; - /// /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction = null, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { IPublicClientApplication app = GetClient(parameters.Account, parameters.Environment).AsPublicClient(); - Task task = Task.Factory.StartNew(() => - app.AcquireTokenWithDeviceCode(parameters.Scopes, deviceCodeResult => - { - message = deviceCodeResult.Message; - return Task.CompletedTask; - }).ExecuteAsync(cancellationToken).GetAwaiter().GetResult()); - - while (true) + ServiceClientTracing.Information($"[DeviceCodeAuthenticator] Calling AcquireTokenWithDeviceCode - Scopes: '{string.Join(", ", parameters.Scopes)}'"); + return await app.AcquireTokenWithDeviceCode(parameters.Scopes, deviceCodeResult => { - if (!string.IsNullOrEmpty(message)) - { - promptAction(message); - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - Thread.Sleep(1000); - } - - await Task.CompletedTask; - - return task.Result; + WriteWarning(deviceCodeResult.Message); + return Task.CompletedTask; + }).ExecuteAsync(cancellationToken).ConfigureAwait(false); } /// @@ -65,5 +46,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { return parameters is DeviceCodeParameters; } + + private void WriteWarning(string message) + { + if (PartnerSession.Instance.TryGetComponent("WriteWarning", out EventHandler writeWarningEvent)) + { + writeWarningEvent(this, new StreamEventArgs { Resource = message }); + } + } } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/IAuthenticator.cs b/src/PowerShell/Authenticators/IAuthenticator.cs index 528ed45..f9d0dc3 100644 --- a/src/PowerShell/Authenticators/IAuthenticator.cs +++ b/src/PowerShell/Authenticators/IAuthenticator.cs @@ -3,7 +3,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { - using System; using System.Threading; using System.Threading.Tasks; using Identity.Client; @@ -22,28 +21,29 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction, CancellationToken cancellationToken = default); + Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default); /// /// Determine if this authenticator can apply to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// true if this authenticator can apply; otherwise false. + /// + /// An instance of that represents the access token generated as result of a successful authenication. + /// bool CanAuthenticate(AuthenticationParameters parameters); /// /// Determine if this request can be authenticated using the given authenticator, and authenticate if it can. /// /// The complex object containing authentication specific information. - /// The token based authentication information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// true if the request can be authenticated; otherwise false. - bool TryAuthenticate(AuthenticationParameters parameters, out Task token, Action promptAction = null, CancellationToken cancellationToken = default); + /// + /// An instance of that represents the access token generated as result of a successful authenication. + /// + Task TryAuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/InteractiveUserAuthenticator.cs b/src/PowerShell/Authenticators/InteractiveUserAuthenticator.cs index 67b1b2d..dfe52cd 100644 --- a/src/PowerShell/Authenticators/InteractiveUserAuthenticator.cs +++ b/src/PowerShell/Authenticators/InteractiveUserAuthenticator.cs @@ -4,7 +4,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { using System; - using System.Collections.Generic; using System.Collections.Specialized; using System.Net; using System.Net.Sockets; @@ -14,7 +13,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators using Extensions; using Identity.Client; using Identity.Client.Extensibility; + using Models; + using Models.Authentication; using Network; + using Rest; /// /// Provides the ability to authenticate using an interactive interface. @@ -25,23 +27,17 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction = null, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { AuthenticationResult authResult; IClientApplicationBase app; InteractiveParameters interactiveParameters = parameters as InteractiveParameters; - Task> task; TcpListener listener = null; - int count = 0; string redirectUri = null; - - Queue messages; - int port = 8399; while (++port < 9000) @@ -56,62 +52,42 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators } catch (Exception ex) { - promptAction($"Port {port} is taken with exception '{ex.Message}'; trying to connect to the next port."); + WriteWarning($"Port {port} is taken with exception '{ex.Message}'; trying to connect to the next port."); listener?.Stop(); } } app = GetClient(parameters.Account, parameters.Environment, redirectUri); - messages = new Queue(); if (app is IConfidentialClientApplication) { - ICustomWebUi customWebUi = new DefaultOsBrowserWebUi(messages, interactiveParameters.Message); + ICustomWebUi customWebUi = new DefaultOsBrowserWebUi(interactiveParameters.Message); - task = Task>.Factory.StartNew(async () => - { - Uri authCodeUrl = await customWebUi.AcquireAuthorizationCodeAsync( - await app.AsConfidentialClient().GetAuthorizationRequestUrl(parameters.Scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false), - new Uri(redirectUri), - cancellationToken).ConfigureAwait(false); + ServiceClientTracing.Information($"[InteractiveUserAuthenticator] Calling AcquireAuthorizationCodeAsync - Scopes: '{string.Join(",", parameters.Scopes)}'"); - NameValueCollection queryStringParameters = HttpUtility.ParseQueryString(authCodeUrl.Query); + Uri authCodeUrl = await customWebUi.AcquireAuthorizationCodeAsync( + await app.AsConfidentialClient().GetAuthorizationRequestUrl(parameters.Scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false), + new Uri(redirectUri), + cancellationToken).ConfigureAwait(false); - return await app.AsConfidentialClient().AcquireTokenByAuthorizationCode( - parameters.Scopes, - queryStringParameters["code"]).ExecuteAsync(cancellationToken).ConfigureAwait(false); - }); + NameValueCollection queryStringParameters = HttpUtility.ParseQueryString(authCodeUrl.Query); + + ServiceClientTracing.Information($"[InteractiveUserAuthenticator] Calling AcquireTokenByAuthorizationCode - Scopes: '{string.Join(",", parameters.Scopes)}'"); + + authResult = await app.AsConfidentialClient().AcquireTokenByAuthorizationCode( + parameters.Scopes, + queryStringParameters["code"]).ExecuteAsync(cancellationToken).ConfigureAwait(false); } else { - task = Task>.Factory.StartNew(async () => - { - return await app.AsPublicClient().AcquireTokenInteractive(parameters.Scopes) - .WithCustomWebUi(new DefaultOsBrowserWebUi(messages, interactiveParameters.Message)) - .WithPrompt(Prompt.ForceLogin) - .ExecuteAsync(cancellationToken).ConfigureAwait(false); - }); + ServiceClientTracing.Information(string.Format("[InteractiveUserAuthenticator] Calling AcquireTokenInteractive - Scopes: '{0}'", string.Join(",", parameters.Scopes))); + + authResult = await app.AsPublicClient().AcquireTokenInteractive(parameters.Scopes) + .WithCustomWebUi(new DefaultOsBrowserWebUi(interactiveParameters.Message)) + .WithPrompt(Prompt.ForceLogin) + .ExecuteAsync(cancellationToken).ConfigureAwait(false); } - while (true) - { - while (messages.Count > 0) - { - promptAction(messages.Dequeue()); - count++; - } - - if (count >= 2) - { - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - Thread.Sleep(1000); - } - - authResult = await task.Result.ConfigureAwait(false); - return authResult; } @@ -124,5 +100,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { return parameters is InteractiveParameters; } + + private void WriteWarning(string message) + { + if (PartnerSession.Instance.TryGetComponent("WriteWarning", out EventHandler writeWarningEvent)) + { + writeWarningEvent(this, new StreamEventArgs { Resource = message }); + } + } } } \ No newline at end of file diff --git a/src/PowerShell/Authenticators/RefreshTokenAuthenticator.cs b/src/PowerShell/Authenticators/RefreshTokenAuthenticator.cs index 5c2bcdb..b46d288 100644 --- a/src/PowerShell/Authenticators/RefreshTokenAuthenticator.cs +++ b/src/PowerShell/Authenticators/RefreshTokenAuthenticator.cs @@ -3,12 +3,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { - using System; using System.Threading; using System.Threading.Tasks; using Extensions; using Identity.Client; using Models.Authentication; + using Rest; /// /// Provides the ability to authenticate using a refresh token. @@ -19,21 +19,24 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction = null, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { IClientApplicationBase app = GetClient(parameters.Account, parameters.Environment); + + ServiceClientTracing.Information("[RefreshTokenAuthenticator] Calling GetAccountsAysnc"); IAccount account = await app.GetAccountAsync(parameters.Account.Identifier).ConfigureAwait(false); if (account != null) { + ServiceClientTracing.Information($"[RefreshTokenAuthenticator] Calling AcquireTokenSilent - Scopes: '{string.Join(", ", parameters.Scopes)}'"); return await app.AcquireTokenSilent(parameters.Scopes, account).ExecuteAsync(cancellationToken).ConfigureAwait(false); } + ServiceClientTracing.Information($"[RefreshTokenAuthenticator] Calling AcquireTokenByRefreshToken - Scopes: '{string.Join(", ", parameters.Scopes)}'"); return await app.AsRefreshTokenClient().AcquireTokenByRefreshToken( parameters.Scopes, parameters.Account.GetProperty(PartnerAccountPropertyType.RefreshToken)).ExecuteAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/PowerShell/Authenticators/ServicePrincipalAuthenticator.cs b/src/PowerShell/Authenticators/ServicePrincipalAuthenticator.cs index 2743c47..56f09f4 100644 --- a/src/PowerShell/Authenticators/ServicePrincipalAuthenticator.cs +++ b/src/PowerShell/Authenticators/ServicePrincipalAuthenticator.cs @@ -3,7 +3,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { - using System; using System.Threading; using System.Threading.Tasks; using Extensions; @@ -18,12 +17,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { IConfidentialClientApplication app = GetClient(parameters.Account, parameters.Environment).AsConfidentialClient(); diff --git a/src/PowerShell/Authenticators/SilentAuthenticator.cs b/src/PowerShell/Authenticators/SilentAuthenticator.cs index fdaadbe..567ed3a 100644 --- a/src/PowerShell/Authenticators/SilentAuthenticator.cs +++ b/src/PowerShell/Authenticators/SilentAuthenticator.cs @@ -3,13 +3,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators { - using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Extensions; using Identity.Client; + using Rest; /// /// Provides the ability to authenticate non-interactively. @@ -20,17 +20,18 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators /// Apply this authenticator to the given authentication parameters. /// /// The complex object containing authentication specific information. - /// The action used to prompt for interaction. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// An instance of that represents the access token generated as result of a successful authenication. /// - public override async Task AuthenticateAsync(AuthenticationParameters parameters, Action promptAction = null, CancellationToken cancellationToken = default) + public override async Task AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default) { IPublicClientApplication app = GetClient(parameters.Account, parameters.Environment).AsPublicClient(); + ServiceClientTracing.Information(string.Format("[SilentAuthenticator] Calling GetAccountsAsync")); IEnumerable accounts = await app.GetAccountsAsync().ConfigureAwait(false); + ServiceClientTracing.Information($"[SilentAuthenticator] Calling AcquireTokenSilent - Scopes: '{string.Join(",", parameters.Scopes)}', UserId: '{((SilentParameters)parameters).UserId}', Number of accounts: '{accounts.Count()}'"); AuthenticationResult authResult = await app.AcquireTokenSilent( parameters.Scopes, accounts.FirstOrDefault(a => a.HomeAccountId.ObjectId.Equals(((SilentParameters)parameters).UserId))) diff --git a/src/PowerShell/Commands/AddPartnerCustomerCartLineItem.cs b/src/PowerShell/Commands/AddPartnerCustomerCartLineItem.cs index bc0b0d4..3541822 100644 --- a/src/PowerShell/Commands/AddPartnerCustomerCartLineItem.cs +++ b/src/PowerShell/Commands/AddPartnerCustomerCartLineItem.cs @@ -51,7 +51,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess(string.Format(CultureInfo.CurrentCulture, Resources.AddPartnerCustomerCartLineItemWhatIf, CartId))) { - cart = Partner.Customers[CustomerId].Carts[CartId].GetAsync().GetAwaiter().GetResult(); + cart = Partner.Customers[CustomerId].Carts[CartId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); lineItems = cart.LineItems.ToList(); cartLineItem = new CartLineItem(); diff --git a/src/PowerShell/Commands/AddPartnerCustomerUserRoleMember.cs b/src/PowerShell/Commands/AddPartnerCustomerUserRoleMember.cs index e1edd77..da3979c 100644 --- a/src/PowerShell/Commands/AddPartnerCustomerUserRoleMember.cs +++ b/src/PowerShell/Commands/AddPartnerCustomerUserRoleMember.cs @@ -71,7 +71,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); userId.AssertNotEmpty(nameof(userId)); - return Partner.Customers[customerId].Users[userId].GetAsync().GetAwaiter().GetResult(); ; + return Partner.Customers[customerId].Users[userId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); ; } } diff --git a/src/PowerShell/Commands/ConnectPartnerCenter.cs b/src/PowerShell/Commands/ConnectPartnerCenter.cs index 44e3547..2757ed7 100644 --- a/src/PowerShell/Commands/ConnectPartnerCenter.cs +++ b/src/PowerShell/Commands/ConnectPartnerCenter.cs @@ -18,7 +18,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// [Cmdlet(VerbsCommunications.Connect, "PartnerCenter", DefaultParameterSetName = UserParameterSet, SupportsShouldProcess = true)] [OutputType(typeof(PartnerContext))] - public class ConnectPartnerCenter : PartnerPSCmdlet, IModuleAssemblyInitializer + public class ConnectPartnerCenter : PartnerAsyncCmdlet, IModuleAssemblyInitializer { /// /// The name of the access token parameter set. @@ -166,9 +166,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands } /// - /// Performs the operations associated with the command. + /// Executes the operations associated with the cmdlet. /// - protected override void ProcessRecord() + public override void ExecuteCmdlet() { IPartner partnerOperations; OrganizationProfile profile; @@ -231,37 +231,38 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands $"{environment.AzureAdGraphEndpoint}/.default" : $"{environment.PartnerCenterEndpoint}/user_impersonation"); - account.Tenant = string.IsNullOrEmpty(Tenant) ? "common" : Tenant; + account.Tenant = string.IsNullOrEmpty(Tenant) ? "organizations" : Tenant; - PartnerSession.Instance.AuthenticationFactory.Authenticate( - account, - environment, - new[] { account.GetProperty(PartnerAccountPropertyType.Scope) }, - Message, - WriteWarning, - WriteDebug, - CancellationToken); - - PartnerSession.Instance.Context = new PartnerContext + Scheduler.RunTask(async () => { - Account = account, - Environment = environment - }; + await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( + account, + environment, + new[] { account.GetProperty(PartnerAccountPropertyType.Scope) }, + Message, + CancellationToken).ConfigureAwait(false); - try - { - partnerOperations = PartnerSession.Instance.ClientFactory.CreatePartnerOperations(); - profile = partnerOperations.Profiles.OrganizationProfile.GetAsync().GetAwaiter().GetResult(); + PartnerSession.Instance.Context = new PartnerContext + { + Account = account, + Environment = environment + }; - PartnerSession.Instance.Context.CountryCode = profile.DefaultAddress.Country; - PartnerSession.Instance.Context.Locale = profile.Culture; - } - catch (PartnerException) - { - /* This error can safely be ignored */ - } + try + { + partnerOperations = PartnerSession.Instance.ClientFactory.CreatePartnerOperations(); + profile = await partnerOperations.Profiles.OrganizationProfile.GetAsync().ConfigureAwait(false); - WriteObject(PartnerSession.Instance.Context); + PartnerSession.Instance.Context.CountryCode = profile.DefaultAddress.Country; + PartnerSession.Instance.Context.Locale = profile.Culture; + } + catch (PartnerException) + { + /* This error can safely be ignored */ + } + + WriteObject(PartnerSession.Instance.Context); + }); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerAgreementDetail.cs b/src/PowerShell/Commands/GetPartnerAgreementDetail.cs index b8bc91f..990895c 100644 --- a/src/PowerShell/Commands/GetPartnerAgreementDetail.cs +++ b/src/PowerShell/Commands/GetPartnerAgreementDetail.cs @@ -32,15 +32,15 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (string.IsNullOrEmpty(AgreementType)) { - agreements = Partner.AgreementDetails.GetAsync().GetAwaiter().GetResult(); + agreements = Partner.AgreementDetails.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } else if (AgreementType.Equals("All", StringComparison.InvariantCultureIgnoreCase)) { - agreements = Partner.AgreementDetails.ByAgreementType("*").GetAsync().GetAwaiter().GetResult(); + agreements = Partner.AgreementDetails.ByAgreementType("*").GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } else { - agreements = Partner.AgreementDetails.ByAgreementType(AgreementType).GetAsync().GetAwaiter().GetResult(); + agreements = Partner.AgreementDetails.ByAgreementType(AgreementType).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } WriteObject(agreements.Items.Select(a => new PSAgreementMetaData(a)), true); diff --git a/src/PowerShell/Commands/GetPartnerAgreementStatus.cs b/src/PowerShell/Commands/GetPartnerAgreementStatus.cs new file mode 100644 index 0000000..5d210c6 --- /dev/null +++ b/src/PowerShell/Commands/GetPartnerAgreementStatus.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Commands +{ + using System.Management.Automation; + using System.Text.RegularExpressions; + using PartnerCenter.Models.Compliance; + + [Cmdlet(VerbsCommon.Get, "PartnerAgreementStatus", DefaultParameterSetName = "ByTenantId")] + [OutputType(typeof(AgreementSignatureStatus))] + public class GetPartnerAgreementStatus : PartnerCmdlet + { + /// + /// Gets or sets the Microsoft Partner Network (MPN) identifier. + /// + [Parameter(HelpMessage = "The Microsoft Partner Network (MPN) identifier for the partner.", Mandatory = true, ParameterSetName = "ByMpnId", Position = 0)] + public string MpnId { get; set; } + + /// + /// Gets or sets the tenant identifier. + /// + [Parameter(HelpMessage = "The tenant identifier for the partner.", Mandatory = true, ParameterSetName = "ByTenantId", Position = 0)] + [ValidatePattern(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", Options = RegexOptions.Compiled | RegexOptions.IgnoreCase)] + public string TenantId { get; set; } + + /// + /// Executes the operations associated with the cmdlet. + /// + public override void ExecuteCmdlet() + { + WriteObject(Partner.Compliance.AgreementSignatureStatus.GetAsync(MpnId, TenantId, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult()); + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerAuditRecord.cs b/src/PowerShell/Commands/GetPartnerAuditRecord.cs index 5ff4ee4..45a6d21 100644 --- a/src/PowerShell/Commands/GetPartnerAuditRecord.cs +++ b/src/PowerShell/Commands/GetPartnerAuditRecord.cs @@ -59,7 +59,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands while (enumerator.HasValue) { records.AddRange(enumerator.Current.Items.Select(r => new PSAuditRecord(r))); - enumerator.NextAsync().GetAwaiter().GetResult(); + enumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/src/PowerShell/Commands/GetPartnerAzureBillingAccount.cs b/src/PowerShell/Commands/GetPartnerAzureBillingAccount.cs index 466aa76..3ced03e 100644 --- a/src/PowerShell/Commands/GetPartnerAzureBillingAccount.cs +++ b/src/PowerShell/Commands/GetPartnerAzureBillingAccount.cs @@ -9,16 +9,20 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Models.Authentication; [Cmdlet(VerbsCommon.Get, "PartnerAzureBillingAccount"), OutputType(typeof(BillingAccount))] - public class GetPartnerAzureBillingAccount : PartnerPSCmdlet + public class GetPartnerAzureBillingAccount : PartnerAsyncCmdlet { /// /// Executes the operations associated with the cmdlet. /// public override void ExecuteCmdlet() { - IBillingManagementClient client = PartnerSession.Instance.ClientFactory.CreateServiceClient(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); + Scheduler.RunTask(async () => + { + IBillingManagementClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); + BillingAccountListResult data = await client.BillingAccounts.ListAsync(null, CancellationToken).ConfigureAwait(false); - WriteObject(client.BillingAccounts.ListAsync(null, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult().Value, true); + WriteObject(data.Value, true); + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerAzureBillingPolicy.cs b/src/PowerShell/Commands/GetPartnerAzureBillingPolicy.cs new file mode 100644 index 0000000..63a5555 --- /dev/null +++ b/src/PowerShell/Commands/GetPartnerAzureBillingPolicy.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Commands +{ + using System.Management.Automation; + using System.Text.RegularExpressions; + using Azure.Management.Billing; + using Azure.Management.Billing.Models; + using Models.Authentication; + + [Cmdlet(VerbsCommon.Get, "PartnerAzureBillingPolicy")] + [OutputType(typeof(CustomerPolicy))] + public class GetPartnerAzureBillingPolicy : PartnerAsyncCmdlet + { + /// + /// Gets or sets the name for the billing account. + /// + [Parameter(HelpMessage = "The name for the billing account.", Mandatory = true)] + public string BillingAccountName { get; set; } + + /// + /// Gets or sets the identifier for the customer. + /// + [Parameter(HelpMessage = "The identifier for the customer.", Mandatory = true)] + [ValidatePattern(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", Options = RegexOptions.Compiled | RegexOptions.IgnoreCase)] + public string CustomerId { get; set; } + + /// + /// Executes the operations associated with the cmdlet. + /// + public override void ExecuteCmdlet() + { + Scheduler.RunTask(async () => + { + IBillingManagementClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); + + WriteObject(client.Policies.GetByCustomerAsync(BillingAccountName, CustomerId, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult()); + }, true); + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerAzureBillingProfile.cs b/src/PowerShell/Commands/GetPartnerAzureBillingProfile.cs index b497867..2958917 100644 --- a/src/PowerShell/Commands/GetPartnerAzureBillingProfile.cs +++ b/src/PowerShell/Commands/GetPartnerAzureBillingProfile.cs @@ -10,12 +10,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Rest.Azure; [Cmdlet(VerbsCommon.Get, "PartnerAzureBillingProfile"), OutputType(typeof(BillingAccount))] - public class GetPartnerAzureBillingProfile : PartnerPSCmdlet + public class GetPartnerAzureBillingProfile : PartnerAsyncCmdlet { /// /// Gets or set the name for the billing account. /// [Parameter(HelpMessage = "The name for the billing account", Mandatory = true)] + [ValidateNotNullOrEmpty] public string BillingAccountName { get; set; } /// @@ -23,10 +24,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - IBillingManagementClient client = PartnerSession.Instance.ClientFactory.CreateServiceClient(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}//user_impersonation" }); - IPage data = client.Customers.ListByBillingAccountAsync(BillingAccountName, null, null, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + Scheduler.RunTask(async () => + { + IBillingManagementClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}//user_impersonation" }); + IPage data = client.Customers.ListByBillingAccountAsync(BillingAccountName, null, null, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); - WriteObject(data, true); + WriteObject(data, true); + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerBillingProfile.cs b/src/PowerShell/Commands/GetPartnerBillingProfile.cs index 4aba620..cb6804e 100644 --- a/src/PowerShell/Commands/GetPartnerBillingProfile.cs +++ b/src/PowerShell/Commands/GetPartnerBillingProfile.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSBillingProfile(Partner.Profiles.BillingProfile.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSBillingProfile(Partner.Profiles.BillingProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCountryValidation.cs b/src/PowerShell/Commands/GetPartnerCountryValidation.cs index 3163972..77f2956 100644 --- a/src/PowerShell/Commands/GetPartnerCountryValidation.cs +++ b/src/PowerShell/Commands/GetPartnerCountryValidation.cs @@ -20,7 +20,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCountryValidationRules(Partner.CountryValidationRules.ByCountry(CountryCode).GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCountryValidationRules(Partner.CountryValidationRules.ByCountry(CountryCode).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomer.cs b/src/PowerShell/Commands/GetPartnerCustomer.cs index 09e36eb..f42d1da 100644 --- a/src/PowerShell/Commands/GetPartnerCustomer.cs +++ b/src/PowerShell/Commands/GetPartnerCustomer.cs @@ -21,7 +21,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// [Cmdlet(VerbsCommon.Get, "PartnerCustomer", DefaultParameterSetName = "ById")] [OutputType(typeof(PSCustomer))] - public class GetPartnerCustomer : PartnerCmdlet + public class GetPartnerCustomer : PartnerAsyncCmdlet { /// /// Gets or sets the optional customer identifier. @@ -46,49 +46,53 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - IResourceCollectionEnumerator> customersEnumerator; - List customers = new List(); - SeekBasedResourceCollection seekCustomers; - - if (!string.IsNullOrEmpty(CustomerId)) + Scheduler.RunTask(async () => { - WriteObject(new PSCustomer(Partner.Customers[CustomerId].GetAsync().GetAwaiter().GetResult())); - return; - } - if (ParameterSetName.Equals("ByDomain", StringComparison.InvariantCultureIgnoreCase)) - { - Graph.GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as Graph.GraphServiceClient; - client.AuthenticationProvider = new GraphAuthenticationProvider(); + IPartner partner = await PartnerSession.Instance.ClientFactory.CreatePartnerOperationsAsync(); + IResourceCollectionEnumerator> customersEnumerator; + List customers = new List(); + SeekBasedResourceCollection seekCustomers; - Graph.IGraphServiceContractsCollectionPage data = client.Contracts.Request().Filter($"defaultDomainName eq '{Domain}'").GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - if (data.CurrentPage != null && data.CurrentPage.Any()) + if (!string.IsNullOrEmpty(CustomerId)) { - Customer customer = Partner.Customers.ById(data.CurrentPage[0].CustomerId.ToString()).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - WriteObject(new PSCustomer(customer)); + WriteObject(new PSCustomer(await partner.Customers[CustomerId].GetAsync().ConfigureAwait(false))); return; } + if (ParameterSetName.Equals("ByDomain", StringComparison.InvariantCultureIgnoreCase)) + { + Graph.GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as Graph.GraphServiceClient; + client.AuthenticationProvider = new GraphAuthenticationProvider(); - seekCustomers = Partner.Customers.QueryAsync( - QueryFactory.BuildSimpleQuery(new SimpleFieldFilter( - CustomerSearchField.Domain.ToString(), - FieldFilterOperation.StartsWith, - Domain))).ConfigureAwait(false).GetAwaiter().GetResult(); - } - else - { - seekCustomers = Partner.Customers.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - } + Graph.IGraphServiceContractsCollectionPage data = await client.Contracts.Request().Filter($"defaultDomainName eq '{Domain}'").GetAsync().ConfigureAwait(false); - customersEnumerator = Partner.Enumerators.Customers.Create(seekCustomers); + if (data.CurrentPage != null && data.CurrentPage.Any()) + { + Customer customer = await partner.Customers.ById(data.CurrentPage[0].CustomerId.ToString()).GetAsync().ConfigureAwait(false); + WriteObject(new PSCustomer(customer)); + return; + } - while (customersEnumerator.HasValue) - { - customers.AddRange(customersEnumerator.Current.Items); - customersEnumerator.NextAsync().GetAwaiter().GetResult(); - } + seekCustomers = await partner.Customers.QueryAsync( + QueryFactory.BuildSimpleQuery(new SimpleFieldFilter( + CustomerSearchField.Domain.ToString(), + FieldFilterOperation.StartsWith, + Domain))).ConfigureAwait(false); + } + else + { + seekCustomers = await partner.Customers.GetAsync().ConfigureAwait(false); + } - WriteObject(customers.Select(c => new PSCustomer(c)), true); + customersEnumerator = partner.Enumerators.Customers.Create(seekCustomers); + + while (customersEnumerator.HasValue) + { + customers.AddRange(customersEnumerator.Current.Items); + await customersEnumerator.NextAsync().ConfigureAwait(false); + } + + WriteObject(customers.Select(c => new PSCustomer(c)), true); + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerAgreement.cs b/src/PowerShell/Commands/GetPartnerCustomerAgreement.cs index 7a91f0e..613ec2f 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerAgreement.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerAgreement.cs @@ -35,11 +35,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { if (string.IsNullOrEmpty(AgreementType)) { - WriteObject(Partner.Customers[CustomerId].Agreements.GetAsync().GetAwaiter().GetResult().Items.Select(a => new PSAgreement(a)), true); + WriteObject(Partner.Customers[CustomerId].Agreements.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items.Select(a => new PSAgreement(a)), true); } else { - WriteObject(Partner.Customers[CustomerId].Agreements.ByAgreementType(AgreementType).GetAsync().GetAwaiter().GetResult().Items.Select(a => new PSAgreement(a)), true); + WriteObject(Partner.Customers[CustomerId].Agreements.ByAgreementType(AgreementType).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items.Select(a => new PSAgreement(a)), true); } } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerAzurePlanEntitlement.cs b/src/PowerShell/Commands/GetPartnerCustomerAzurePlanEntitlement.cs index 6d7ad7c..66e24fb 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerAzurePlanEntitlement.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerAzurePlanEntitlement.cs @@ -3,15 +3,17 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { + using System.Linq; using System.Management.Automation; using System.Text.RegularExpressions; + using Models.Subscriptions; using PartnerCenter.Models; using PartnerCenter.Models.Subscriptions; /// /// Gets a list of Azure Plan entitlements for a customer from Partner Center. /// - [Cmdlet(VerbsCommon.Get, "PartnerCustomerAzurePlanEntitlement"), OutputType(typeof(AzureEntitlement))] + [Cmdlet(VerbsCommon.Get, "PartnerCustomerAzurePlanEntitlement"), OutputType(typeof(PSAzureEntitlement))] public class GetPartnerCustomerAzurePlanEntitlement : PartnerCmdlet { /// @@ -40,7 +42,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands .GetAwaiter() .GetResult(); - WriteObject(entitlements, true); + WriteObject(entitlements.Items.Select(e => new PSAzureEntitlement(e)), true); } } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerBillingProfile.cs b/src/PowerShell/Commands/GetPartnerCustomerBillingProfile.cs index 9eb81bc..ff8a54c 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerBillingProfile.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerBillingProfile.cs @@ -22,7 +22,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCustomerBillingProfile(Partner.Customers[CustomerId].Profiles.Billing.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCustomerBillingProfile(Partner.Customers[CustomerId].Profiles.Billing.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerCart.cs b/src/PowerShell/Commands/GetPartnerCustomerCart.cs index e8f18e4..aadaeac 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerCart.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerCart.cs @@ -29,7 +29,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCart(Partner.Customers[CustomerId].Carts[CartId].GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCart(Partner.Customers[CustomerId].Carts[CartId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerCompanyProfile.cs b/src/PowerShell/Commands/GetPartnerCustomerCompanyProfile.cs index ece2380..ef58005 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerCompanyProfile.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerCompanyProfile.cs @@ -25,7 +25,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCustomerCompanyProfile(Partner.Customers[CustomerId].Profiles.Company.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCustomerCompanyProfile(Partner.Customers[CustomerId].Profiles.Company.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerConfigurationPolicy.cs b/src/PowerShell/Commands/GetPartnerCustomerConfigurationPolicy.cs index 8488d17..74aed3d 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerConfigurationPolicy.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerConfigurationPolicy.cs @@ -62,7 +62,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands IEnumerable devicePolicy; customerId.AssertNotEmpty(nameof(customerId)); - devicePolicy = Partner.Customers[customerId].ConfigurationPolicies.GetAsync().GetAwaiter().GetResult().Items; + devicePolicy = Partner.Customers[customerId].ConfigurationPolicies.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items; WriteObject(devicePolicy.Select(d => new PSConfigurationPolicy(d)), true); } @@ -83,7 +83,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); policyId.AssertNotEmpty(nameof(policyId)); - devicePolicy = Partner.Customers[customerId].ConfigurationPolicies[policyId].GetAsync().GetAwaiter().GetResult(); + devicePolicy = Partner.Customers[customerId].ConfigurationPolicies[policyId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSConfigurationPolicy(devicePolicy), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerDevice.cs b/src/PowerShell/Commands/GetPartnerCustomerDevice.cs index 7c9f2ff..38c1738 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerDevice.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerDevice.cs @@ -35,7 +35,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - IEnumerable devices = Partner.Customers[CustomerId].DeviceBatches[BatchId].Devices.GetAsync().GetAwaiter().GetResult().Items; + IEnumerable devices = Partner.Customers[CustomerId].DeviceBatches[BatchId].Devices.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items; WriteObject(devices.Select(d => new PSDevice(d)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerDeviceBatch.cs b/src/PowerShell/Commands/GetPartnerCustomerDeviceBatch.cs index a66e0f8..cfc5afa 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerDeviceBatch.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerDeviceBatch.cs @@ -28,7 +28,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - IEnumerable deviceBatch = Partner.Customers[CustomerId].DeviceBatches.GetAsync().GetAwaiter().GetResult().Items; + IEnumerable deviceBatch = Partner.Customers[CustomerId].DeviceBatches.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items; WriteObject(deviceBatch.Select(db => new PSDeviceBatch(db)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerLicenseDeploymentInfo.cs b/src/PowerShell/Commands/GetPartnerCustomerLicenseDeploymentInfo.cs index 4782bff..b60037b 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerLicenseDeploymentInfo.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerLicenseDeploymentInfo.cs @@ -27,7 +27,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection insights; - insights = Partner.Customers[CustomerId].Analytics.Licenses.Deployment.GetAsync().GetAwaiter().GetResult(); + insights = Partner.Customers[CustomerId].Analytics.Licenses.Deployment.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(insights.Items.Select(i => new PSCustomerLicensesDeploymentInsights(i)), true); } diff --git a/src/PowerShell/Commands/GetPartnerCustomerManagedService.cs b/src/PowerShell/Commands/GetPartnerCustomerManagedService.cs index e1bc488..7c4bd3c 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerManagedService.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerManagedService.cs @@ -59,7 +59,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); - managedServices = Partner.Customers.ById(CustomerId).ManagedServices.GetAsync().GetAwaiter().GetResult(); + managedServices = Partner.Customers.ById(CustomerId).ManagedServices.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (managedServices.TotalCount > 0) { @@ -84,7 +84,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); managedServiceId.AssertNotEmpty(nameof(managedServiceId)); - managedServices = Partner.Customers.ById(CustomerId).ManagedServices.GetAsync().GetAwaiter().GetResult(); + managedServices = Partner.Customers.ById(CustomerId).ManagedServices.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (managedServices.TotalCount > 0) { diff --git a/src/PowerShell/Commands/GetPartnerCustomerOrderLineItemActivationLink.cs b/src/PowerShell/Commands/GetPartnerCustomerOrderLineItemActivationLink.cs index 01df1f2..bb02670 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerOrderLineItemActivationLink.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerOrderLineItemActivationLink.cs @@ -40,7 +40,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(Partner.Customers[CustomerId].Orders[OrderId].OrderLineItems[OrderLineItemNumber].ActivationLink.GetAsync().GetAwaiter().GetResult().Items, true); + WriteObject(Partner.Customers[CustomerId].Orders[OrderId].OrderLineItems[OrderLineItemNumber].ActivationLink.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerOrderProvisioningStatus.cs b/src/PowerShell/Commands/GetPartnerCustomerOrderProvisioningStatus.cs index 3963d29..53c4161 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerOrderProvisioningStatus.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerOrderProvisioningStatus.cs @@ -31,7 +31,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(Partner.Customers[CustomerId].Orders[OrderId].ProvisioningStatus.GetAsync().GetAwaiter().GetResult().Items.Select(s => new PSOrderLineItemProvisioningStatus(s))); + WriteObject(Partner.Customers[CustomerId].Orders[OrderId].ProvisioningStatus.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items.Select(s => new PSOrderLineItemProvisioningStatus(s))); } } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerQualification.cs b/src/PowerShell/Commands/GetPartnerCustomerQualification.cs index 3852c39..44d4e7f 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerQualification.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerQualification.cs @@ -25,7 +25,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(Partner.Customers[CustomerId].Qualification.GetAsync().GetAwaiter().GetResult()); + WriteObject(Partner.Customers[CustomerId].Qualification.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult()); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerServiceCosts.cs b/src/PowerShell/Commands/GetPartnerCustomerServiceCosts.cs index 935a135..6884a42 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerServiceCosts.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerServiceCosts.cs @@ -34,7 +34,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection lineItems; - lineItems = Partner.Customers[CustomerId].ServiceCosts.ByBillingPeriod(BillingPeriod).LineItems.GetAsync().GetAwaiter().GetResult(); + lineItems = Partner.Customers[CustomerId].ServiceCosts.ByBillingPeriod(BillingPeriod).LineItems.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(lineItems.Items.Select(i => new PSServiceCostLineItem(i)), true); } diff --git a/src/PowerShell/Commands/GetPartnerCustomerServiceCostsSummary.cs b/src/PowerShell/Commands/GetPartnerCustomerServiceCostsSummary.cs index 8f55f17..e378555 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerServiceCostsSummary.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerServiceCostsSummary.cs @@ -30,7 +30,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ServiceCostsSummary summary = Partner.Customers[CustomerId].ServiceCosts.ByBillingPeriod(BillingPeriod).Summary.GetAsync().GetAwaiter().GetResult(); + ServiceCostsSummary summary = Partner.Customers[CustomerId].ServiceCosts.ByBillingPeriod(BillingPeriod).Summary.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSServiceCostsSummary(summary)); } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscription.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscription.cs index 4da9df3..cc0edef 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscription.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscription.cs @@ -11,9 +11,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Models.Subscriptions; using PartnerCenter.Models; using PartnerCenter.Models.Subscriptions; + using System.Threading.Tasks; + using Models.Authentication; - [Cmdlet(VerbsCommon.Get, "PartnerCustomerSubscription", DefaultParameterSetName = "ByCustomer"), OutputType(typeof(PSSubscription))] - public class GetPartnerCustomerSubscription : PartnerCmdlet + [Cmdlet(VerbsCommon.Get, "PartnerCustomerSubscription", DefaultParameterSetName = "ByCustomer")] + [OutputType(typeof(PSSubscription))] + public class GetPartnerCustomerSubscription : PartnerAsyncCmdlet { /// /// Gets or sets the customer object used to scope the request. @@ -62,48 +65,35 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - string customerId = (InputObject == null) ? CustomerId : InputObject.CustomerId; - - - if (string.IsNullOrEmpty(SubscriptionId)) + Scheduler.RunTask(async () => { - GetSubscriptions(customerId, MpnId, OrderId); - } - else - { - GetSubscription(customerId, SubscriptionId); - } - } + IPartner partner = await PartnerSession.Instance.ClientFactory.CreatePartnerOperationsAsync(); + string customerId = (InputObject == null) ? CustomerId : InputObject.CustomerId; - private void GetSubscription(string customerId, string subscriptionId) - { - Subscription subscription; + if (string.IsNullOrEmpty(SubscriptionId)) + { + ResourceCollection subscriptions; - customerId.AssertNotEmpty(nameof(customerId)); - subscriptionId.AssertNotEmpty(nameof(subscriptionId)); + if (!string.IsNullOrWhiteSpace(MpnId)) + { + subscriptions = await partner.Customers[customerId].Subscriptions.ByPartner(MpnId).GetAsync().ConfigureAwait(false); + } + else if (!string.IsNullOrWhiteSpace(OrderId)) + { + subscriptions = await partner.Customers[customerId].Subscriptions.ByOrder(OrderId).GetAsync().ConfigureAwait(false); + } + else + { + subscriptions = await partner.Customers[customerId].Subscriptions.GetAsync().ConfigureAwait(false); + } - subscription = Partner.Customers[customerId].Subscriptions[subscriptionId].GetAsync().GetAwaiter().GetResult(); - WriteObject(new PSSubscription(subscription)); - } - - private void GetSubscriptions(string customerId, string mpnId = null, string orderId = null) - { - ResourceCollection subscriptions; - - if (!string.IsNullOrWhiteSpace(mpnId)) - { - subscriptions = Partner.Customers[customerId].Subscriptions.ByPartner(mpnId).GetAsync().GetAwaiter().GetResult(); - } - else if (!string.IsNullOrWhiteSpace(orderId)) - { - subscriptions = Partner.Customers[customerId].Subscriptions.ByOrder(orderId).GetAsync().GetAwaiter().GetResult(); - } - else - { - subscriptions = Partner.Customers[customerId].Subscriptions.GetAsync().GetAwaiter().GetResult(); - } - - WriteObject(subscriptions.Items.Select(s => new PSSubscription(s)), true); + WriteObject(subscriptions.Items.Select(s => new PSSubscription(s)), true); + } + else + { + WriteObject(new PSSubscription(await partner.Customers[customerId].Subscriptions[SubscriptionId].GetAsync().ConfigureAwait(false))); + } + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionAddOn.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionAddOn.cs index 0cbaf3e..215b386 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionAddOn.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionAddOn.cs @@ -35,7 +35,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection subscripions = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].AddOns.GetAsync().GetAwaiter().GetResult(); + ResourceCollection subscripions = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].AddOns.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(subscripions.Items.Select(s => new PSSubscription(s)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionMeterUsage.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionMeterUsage.cs index db69be0..05c24b1 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionMeterUsage.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionMeterUsage.cs @@ -34,7 +34,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection usageRecords; - usageRecords = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].UsageRecords.ByMeter.GetAsync().GetAwaiter().GetResult(); + usageRecords = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].UsageRecords.ByMeter.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(usageRecords.Items.Select(r => new PSMeterUsageRecord(r)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionProvisioningStatus.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionProvisioningStatus.cs index cb525b0..b1f4bf2 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionProvisioningStatus.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionProvisioningStatus.cs @@ -33,7 +33,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - SubscriptionProvisioningStatus status = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].ProvisioningStatus.GetAsync().GetAwaiter().GetResult(); + SubscriptionProvisioningStatus status = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].ProvisioningStatus.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSSubscriptionProvisioningStatus(status)); } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionRegistrationStatus.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionRegistrationStatus.cs index d0cc348..9db5801 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionRegistrationStatus.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionRegistrationStatus.cs @@ -33,7 +33,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - SubscriptionRegistrationStatus status = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].RegistrationStatus.GetAsync().GetAwaiter().GetResult(); + SubscriptionRegistrationStatus status = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].RegistrationStatus.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSSubscriptionRegistrationStatus(status)); } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionResourceUsage.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionResourceUsage.cs index 8a3980d..1b6f2a6 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionResourceUsage.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionResourceUsage.cs @@ -34,7 +34,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection usageRecords; - usageRecords = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].UsageRecords.ByResource.GetAsync().GetAwaiter().GetResult(); + usageRecords = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].UsageRecords.ByResource.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(usageRecords.Items.Select(r => new PSResourceUsageRecord(r)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionSupportContact.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionSupportContact.cs index cd3c6ec..99cb008 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionSupportContact.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionSupportContact.cs @@ -30,7 +30,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - SupportContact contact = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].SupportContact.GetAsync().GetAwaiter().GetResult(); + SupportContact contact = Partner.Customers[CustomerId].Subscriptions[SubscriptionId].SupportContact.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSSupportContact(contact)); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUpgrades.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUpgrades.cs index 0335d1e..7ef3a5e 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUpgrades.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUpgrades.cs @@ -37,7 +37,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection upgrades; - upgrades = Partner.Customers.ById(CustomerId).Subscriptions.ById(SubscriptionId).Upgrades.GetAsync().GetAwaiter().GetResult(); + upgrades = Partner.Customers.ById(CustomerId).Subscriptions.ById(SubscriptionId).Upgrades.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(upgrades.Items.Select(c => new PSCustomerSubscriptionUpgrades(c)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUtilization.cs b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUtilization.cs index 39825b6..9c3bf2d 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUtilization.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerSubscriptionUtilization.cs @@ -8,8 +8,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using System.Linq; using System.Management.Automation; using System.Text.RegularExpressions; - using System.Threading.Tasks; using Enumerators; + using Models.Authentication; using Models.Utilizations; using PartnerCenter.Models; using PartnerCenter.Models.Utilizations; @@ -17,8 +17,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Cmdlet used to obtain Azure utilization records for the specified subscription. /// - [Cmdlet(VerbsCommon.Get, "PartnerCustomerSubscriptionUtilization"), OutputType(typeof(PSAzureUtilizationRecord))] - public class GetPartnerCustomerSubscriptionUtilization : PartnerCmdlet + [Cmdlet(VerbsCommon.Get, "PartnerCustomerSubscriptionUtilization")] + [OutputType(typeof(PSAzureUtilizationRecord))] + public class GetPartnerCustomerSubscriptionUtilization : PartnerAsyncCmdlet { /// /// Gets or sets the identifier of the customer that owns the subscription. @@ -75,38 +76,38 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - List records = Task.Run(() => RunAsync()).ConfigureAwait(false).GetAwaiter().GetResult(); - - WriteObject(records, true); - } - - public async Task> RunAsync() - { - IResourceCollectionEnumerator> enumerator; - List records = new List(); - ResourceCollection utilizationRecords; - - utilizationRecords = await Partner.Customers[CustomerId] - .Subscriptions[SubscriptionId] - .Utilization.Azure.QueryAsync( - StartDate, - EndDate ?? DateTimeOffset.UtcNow, - Granularity ?? AzureUtilizationGranularity.Daily, - !ShowDetails.IsPresent || ShowDetails.ToBool(), - PageSize == null ? 1000 : PageSize.Value).ConfigureAwait(false); - - if (utilizationRecords?.TotalCount > 0) + Scheduler.RunTask(async () => { - enumerator = Partner.Enumerators.Utilization.Azure.Create(utilizationRecords); + IPartner partner = await PartnerSession.Instance.ClientFactory.CreatePartnerOperationsAsync(); - while (enumerator.HasValue) + IResourceCollectionEnumerator> enumerator; + List records = new List(); + ResourceCollection utilizationRecords; + + utilizationRecords = await partner.Customers[CustomerId] + .Subscriptions[SubscriptionId] + .Utilization.Azure.QueryAsync( + StartDate, + EndDate ?? DateTimeOffset.UtcNow, + Granularity ?? AzureUtilizationGranularity.Daily, + !ShowDetails.IsPresent || ShowDetails.ToBool(), + PageSize == null ? 1000 : PageSize.Value).ConfigureAwait(false); + + if (utilizationRecords?.TotalCount > 0) { - records.AddRange(enumerator.Current.Items.Select(r => new PSAzureUtilizationRecord(r))); - await enumerator.NextAsync().ConfigureAwait(false); - } - } + enumerator = partner.Enumerators.Utilization.Azure.Create(utilizationRecords); - return records; + while (enumerator.HasValue) + { + records.AddRange(enumerator.Current.Items.Select(r => new PSAzureUtilizationRecord(r))); + await enumerator.NextAsync().ConfigureAwait(false); + } + } + + WriteObject(records, true); + + }, true); } + } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerTrialConversion.cs b/src/PowerShell/Commands/GetPartnerCustomerTrialConversion.cs index 5b56b39..d16d771 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerTrialConversion.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerTrialConversion.cs @@ -32,7 +32,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection conversions = Partner.Customers.ById(CustomerId).Subscriptions.ById(SubscriptionId).Conversions.GetAsync().GetAwaiter().GetResult(); + ResourceCollection conversions = Partner.Customers.ById(CustomerId).Subscriptions.ById(SubscriptionId).Conversions.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(conversions.Items.Select(c => new PSCustomerTrialConversion(c)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerCustomerUsageSummary.cs b/src/PowerShell/Commands/GetPartnerCustomerUsageSummary.cs index b1e44f4..6f6e69b 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerUsageSummary.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerUsageSummary.cs @@ -22,7 +22,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCustomerUsageSummary(Partner.Customers[CustomerId].UsageSummary.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCustomerUsageSummary(Partner.Customers[CustomerId].UsageSummary.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerUser.cs b/src/PowerShell/Commands/GetPartnerCustomerUser.cs index 9bd7384..1ec43dd 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerUser.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerUser.cs @@ -94,7 +94,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); userId.AssertNotEmpty(nameof(userId)); - WriteObject(new PSCustomerUser(Partner.Customers[customerId].Users[userId].GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCustomerUser(Partner.Customers[customerId].Users[userId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } /// @@ -114,13 +114,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands users = new List(); - seekUsers = Partner.Customers[customerId].Users.GetAsync().GetAwaiter().GetResult(); + seekUsers = Partner.Customers[customerId].Users.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); usersEnumerator = Partner.Enumerators.CustomerUsers.Create(seekUsers); while (usersEnumerator.HasValue) { users.AddRange(usersEnumerator.Current.Items); - usersEnumerator.NextAsync().GetAwaiter().GetResult(); + usersEnumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } return users; @@ -151,7 +151,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands while (usersEnumerator.HasValue) { users.AddRange(usersEnumerator.Current.Items); - usersEnumerator.NextAsync().GetAwaiter().GetResult(); + usersEnumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } return users; diff --git a/src/PowerShell/Commands/GetPartnerCustomerUserLicense.cs b/src/PowerShell/Commands/GetPartnerCustomerUserLicense.cs index 284368f..f2a8528 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerUserLicense.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerUserLicense.cs @@ -6,6 +6,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using System.Linq; using System.Management.Automation; using System.Text.RegularExpressions; + using Models.Authentication; using Models.Licenses; using PartnerCenter.Models; using PartnerCenter.Models.Licenses; @@ -13,8 +14,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Command that gets the licenses assigned to a user from a customer. /// - [Cmdlet(VerbsCommon.Get, "PartnerCustomerUserLicense"), OutputType(typeof(PSLicense))] - public class GetPartnerCustomerUserLicense : PartnerCmdlet + [Cmdlet(VerbsCommon.Get, "PartnerCustomerUserLicense")] + [OutputType(typeof(PSLicense))] + public class GetPartnerCustomerUserLicense : PartnerAsyncCmdlet { /// /// Gets or sets the required customer identifier. @@ -42,10 +44,16 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection licenses = Partner.Customers[CustomerId] - .Users[UserId].Licenses.GetAsync(LicenseGroup?.Select(item => item).ToList()).GetAwaiter().GetResult(); + Scheduler.RunTask(async () => + { + IPartner partner = await PartnerSession.Instance.ClientFactory.CreatePartnerOperationsAsync(); + + ResourceCollection licenses = await partner.Customers[CustomerId] + .Users[UserId].Licenses.GetAsync(LicenseGroup?.Select(item => item).ToList()); + + WriteObject(licenses.Items.Select(l => new PSLicense(l)), true); + }, true); - WriteObject(licenses.Items.Select(l => new PSLicense(l)), true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerCustomerUserRole.cs b/src/PowerShell/Commands/GetPartnerCustomerUserRole.cs index a3444d3..237af67 100644 --- a/src/PowerShell/Commands/GetPartnerCustomerUserRole.cs +++ b/src/PowerShell/Commands/GetPartnerCustomerUserRole.cs @@ -63,7 +63,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); userId.AssertNotEmpty(nameof(userId)); - roles = Partner.Customers[customerId].Users[userId].DirectoryRoles.GetAsync().GetAwaiter().GetResult().Items; + roles = Partner.Customers[customerId].Users[userId].DirectoryRoles.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items; WriteObject(roles.Select(e => new PSDirectoryRole(e)), true); } @@ -80,7 +80,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); - roles = Partner.Customers[customerId].DirectoryRoles.GetAsync().GetAwaiter().GetResult().Items; + roles = Partner.Customers[customerId].DirectoryRoles.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult().Items; WriteObject(roles.Select(e => new PSDirectoryRole(e)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerIndirectReseller.cs b/src/PowerShell/Commands/GetPartnerIndirectReseller.cs index c4fe870..c4fa5be 100644 --- a/src/PowerShell/Commands/GetPartnerIndirectReseller.cs +++ b/src/PowerShell/Commands/GetPartnerIndirectReseller.cs @@ -36,7 +36,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands } else { - resellers = Partner.Customers[CustomerId].Relationships.GetAsync().GetAwaiter().GetResult(); + resellers = Partner.Customers[CustomerId].Relationships.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } WriteObject(resellers.Items.Select(r => new PSPartnerRelationship(r)), true); diff --git a/src/PowerShell/Commands/GetPartnerInvoice.cs b/src/PowerShell/Commands/GetPartnerInvoice.cs index 42bdd57..3bf5583 100644 --- a/src/PowerShell/Commands/GetPartnerInvoice.cs +++ b/src/PowerShell/Commands/GetPartnerInvoice.cs @@ -35,7 +35,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands } else { - WriteObject(new PSInvoice(Partner.Invoices[InvoiceId].GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSInvoice(Partner.Invoices[InvoiceId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } @@ -45,7 +45,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands List invoices; ResourceCollection resources; - resources = Partner.Invoices.GetAsync().GetAwaiter().GetResult(); + resources = Partner.Invoices.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); enumerator = Partner.Enumerators.Invoices.Create(resources); invoices = new List(); @@ -53,7 +53,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands while (enumerator.HasValue) { invoices.AddRange(enumerator.Current.Items.Select(i => new PSInvoice(i))); - enumerator.NextAsync().GetAwaiter().GetResult(); + enumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } WriteObject(invoices, true); diff --git a/src/PowerShell/Commands/GetPartnerInvoiceLineItem.cs b/src/PowerShell/Commands/GetPartnerInvoiceLineItem.cs index 46880ac..5e0a4f1 100644 --- a/src/PowerShell/Commands/GetPartnerInvoiceLineItem.cs +++ b/src/PowerShell/Commands/GetPartnerInvoiceLineItem.cs @@ -3,9 +3,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { + using System; using System.Collections.Generic; using System.Linq; using System.Management.Automation; + using Models.Authentication; using PartnerCenter.Enumerators; using PartnerCenter.Models; using PartnerCenter.Models.Invoices; @@ -14,94 +16,120 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets a list of line items for the specified invoice from Partner Center. /// - [Cmdlet(VerbsCommon.Get, "PartnerInvoiceLineItem"), OutputType(typeof(PSInvoiceLineItem))] - public class GetPartnerInvoiceLineItem : PartnerCmdlet + [Cmdlet(VerbsCommon.Get, "PartnerInvoiceLineItem", DefaultParameterSetName = ByInvoiceParameterSet)] + [OutputType(typeof(PSInvoiceLineItem))] + public class GetPartnerInvoiceLineItem : PartnerAsyncCmdlet { + /// + /// Name for the by invoice parameter set. + /// + private const string ByInvoiceParameterSet = "ByInvoice"; + + /// + /// Name for the by billing period parameter set. + /// + private const string ByBillingPeriodParameterSet = "ByBillingPeriod"; + /// /// Gets or sets the billing provider. /// - [Parameter(HelpMessage = "The billing provide for the line items.", Mandatory = true)] - [ValidateSet(nameof(BillingProvider.Azure), nameof(BillingProvider.Office), nameof(BillingProvider.OneTime), nameof(BillingProvider.Marketplace))] + [Parameter(HelpMessage = "The billing provide for the line items.", Mandatory = true, ParameterSetName = ByBillingPeriodParameterSet)] + [Parameter(HelpMessage = "The billing provide for the line items.", Mandatory = true, ParameterSetName = ByInvoiceParameterSet)] + [ValidateSet(nameof(BillingProvider.All), nameof(BillingProvider.Azure), nameof(BillingProvider.Office), nameof(BillingProvider.OneTime), nameof(BillingProvider.Marketplace))] public BillingProvider BillingProvider { get; set; } /// /// Gets or sets the currenty code. /// - [Parameter(HelpMessage = "The currency code for the unbilled line items.", Mandatory = false)] + [Parameter(HelpMessage = "The currency code for the unbilled line items.", Mandatory = false, ParameterSetName = ByBillingPeriodParameterSet)] + [Parameter(HelpMessage = "The currency code for the unbilled line items.", Mandatory = false, ParameterSetName = ByInvoiceParameterSet)] [ValidateNotNull] public string CurrencyCode { get; set; } /// /// Gets or set the identifier for the invoice. /// - [Parameter(HelpMessage = "The identifier corresponding to the invoice.", Mandatory = true)] + [Parameter(HelpMessage = "The identifier corresponding to the invoice.", Mandatory = true, ParameterSetName = ByBillingPeriodParameterSet)] + [Parameter(HelpMessage = "The identifier corresponding to the invoice.", Mandatory = true, ParameterSetName = ByInvoiceParameterSet)] [ValidateNotNullOrEmpty] public string InvoiceId { get; set; } /// /// Gets or sets the invoice line item type. /// - [Parameter(HelpMessage = "The type of invoice line items.", Mandatory = true)] + [Parameter(HelpMessage = "The type of invoice line items.", Mandatory = true, ParameterSetName = ByBillingPeriodParameterSet)] + [Parameter(HelpMessage = "The type of invoice line items.", Mandatory = true, ParameterSetName = ByInvoiceParameterSet)] [ValidateSet(nameof(InvoiceLineItemType.BillingLineItems), nameof(InvoiceLineItemType.UsageLineItems))] public InvoiceLineItemType LineItemType { get; set; } + /// + /// Gets or sets the billing period. + /// + [Parameter(HelpMessage = "The billing period for the line items.", Mandatory = true, ParameterSetName = ByBillingPeriodParameterSet)] + [ValidateSet(nameof(BillingPeriod.Current), nameof(BillingPeriod.Previous))] + public BillingPeriod Period { get; set; } + /// /// Executes the operations associated with the cmdlet. /// public override void ExecuteCmdlet() { - IResourceCollectionEnumerator> enumerator; - List items; - ResourceCollection lineItems; + Scheduler.RunTask(async () => + { + IPartner partner = await PartnerSession.Instance.ClientFactory.CreatePartnerOperationsAsync(); + IResourceCollectionEnumerator> enumerator; + List items; + ResourceCollection lineItems; - if (BillingProvider == BillingProvider.Marketplace) - { - lineItems = Partner.Invoices[InvoiceId].By(BillingProvider, LineItemType, CurrencyCode, BillingPeriod.Current).GetAsync().GetAwaiter().GetResult(); - } - else - { - lineItems = Partner.Invoices[InvoiceId].By(BillingProvider, LineItemType).GetAsync().GetAwaiter().GetResult(); - } + if (ParameterSetName.Equals(ByBillingPeriodParameterSet, StringComparison.InvariantCultureIgnoreCase)) + { + lineItems = await partner.Invoices[InvoiceId].By(BillingProvider, LineItemType, CurrencyCode, Period).GetAsync().ConfigureAwait(false); + } + else + { + lineItems = await partner.Invoices[InvoiceId].By(BillingProvider, LineItemType).GetAsync().ConfigureAwait(false); + } - enumerator = Partner.Enumerators.InvoiceLineItems.Create(lineItems); - items = new List(); + enumerator = partner.Enumerators.InvoiceLineItems.Create(lineItems); + items = new List(); - while (enumerator.HasValue) - { - items.AddRange(enumerator.Current.Items); - enumerator.NextAsync().GetAwaiter().GetResult(); - } + while (enumerator.HasValue) + { + items.AddRange(enumerator.Current.Items); + await enumerator.NextAsync().ConfigureAwait(false); + } - if (LineItemType == InvoiceLineItemType.BillingLineItems) - { - if (BillingProvider == BillingProvider.Azure) + if (LineItemType == InvoiceLineItemType.BillingLineItems) { - WriteObject(items.Select(i => new PSUsageBasedLineItem((UsageBasedLineItem)i)), true); + if (BillingProvider == BillingProvider.Azure) + { + WriteObject(items.Select(i => new PSUsageBasedLineItem((UsageBasedLineItem)i)), true); + } + else if (BillingProvider == BillingProvider.Office) + { + WriteObject(items.Select(i => new PSLicenseBasedLineItem((LicenseBasedLineItem)i)), true); + } + else if (BillingProvider == BillingProvider.OneTime) + { + WriteObject(items.Select(i => new PSOneTimeInvoiceLineItem((OneTimeInvoiceLineItem)i)), true); + } + else if (BillingProvider == BillingProvider.Marketplace) + { + WriteObject(items.Select(i => new PSDailyRatedUsageLineItem((DailyRatedUsageLineItem)i)), true); + } } - else if (BillingProvider == BillingProvider.Office) + else { - WriteObject(items.Select(i => new PSLicenseBasedLineItem((LicenseBasedLineItem)i)), true); + if (BillingProvider == BillingProvider.Azure) + { + WriteObject(items.Select(i => new PSDailyUsageLineItem((DailyUsageLineItem)i)), true); + } + else + { + WriteObject(items.Select(i => new PSDailyRatedUsageLineItem((DailyRatedUsageLineItem)i)), true); + } } - else if (BillingProvider == BillingProvider.OneTime) - { - WriteObject(items.Select(i => new PSOneTimeInvoiceLineItem((OneTimeInvoiceLineItem)i)), true); - } - else if (BillingProvider == BillingProvider.Marketplace) - { - WriteObject(items.Select(i => new PSDailyRatedUsageLineItem((DailyRatedUsageLineItem)i)), true); - } - } - else - { - if (BillingProvider == BillingProvider.Azure) - { - WriteObject(items.Select(i => new PSDailyUsageLineItem((DailyUsageLineItem)i)), true); - } - else if (BillingProvider == BillingProvider.Marketplace) - { - WriteObject(items.Select(i => new PSDailyRatedUsageLineItem((DailyRatedUsageLineItem)i)), true); - } - } + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerInvoiceStatement.cs b/src/PowerShell/Commands/GetPartnerInvoiceStatement.cs index 73b5eb6..5e27df5 100644 --- a/src/PowerShell/Commands/GetPartnerInvoiceStatement.cs +++ b/src/PowerShell/Commands/GetPartnerInvoiceStatement.cs @@ -61,7 +61,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands throw new PSInvalidOperationException($"The path already exists: {filePath}. Specify the -Overwrite switch to overwrite the file"); } - using (Stream stream = Partner.Invoices.ById(InvoiceId).Documents.Statement.GetAsync().GetAwaiter().GetResult()) + using (Stream stream = Partner.Invoices.ById(InvoiceId).Documents.Statement.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult()) { FileStream file = File.Create(filePath); stream.Seek(0, SeekOrigin.Begin); diff --git a/src/PowerShell/Commands/GetPartnerInvoiceSummary.cs b/src/PowerShell/Commands/GetPartnerInvoiceSummary.cs index daab13b..384d9d9 100644 --- a/src/PowerShell/Commands/GetPartnerInvoiceSummary.cs +++ b/src/PowerShell/Commands/GetPartnerInvoiceSummary.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection summaries = Partner.Invoices.Summaries.GetAsync().GetAwaiter().GetResult(); + ResourceCollection summaries = Partner.Invoices.Summaries.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(summaries.Items.Select(s => new PSInvoiceSummary(s)), true); } diff --git a/src/PowerShell/Commands/GetPartnerInvoiceTaxReceiptStatement.cs b/src/PowerShell/Commands/GetPartnerInvoiceTaxReceiptStatement.cs index 3a3e625..d2b895e 100644 --- a/src/PowerShell/Commands/GetPartnerInvoiceTaxReceiptStatement.cs +++ b/src/PowerShell/Commands/GetPartnerInvoiceTaxReceiptStatement.cs @@ -72,7 +72,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands try { - using (Stream stream = Partner.Invoices.ById(InvoiceId).Documents.Statement.GetAsync().GetAwaiter().GetResult()) + using (Stream stream = Partner.Invoices.ById(InvoiceId).Documents.Statement.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult()) { fileStream = File.Create(filePath); stream.Seek(0, SeekOrigin.Begin); diff --git a/src/PowerShell/Commands/GetPartnerLegalProfile.cs b/src/PowerShell/Commands/GetPartnerLegalProfile.cs index 78c016d..2bb374e 100644 --- a/src/PowerShell/Commands/GetPartnerLegalProfile.cs +++ b/src/PowerShell/Commands/GetPartnerLegalProfile.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSLegalBusinessProfile(Partner.Profiles.LegalBusinessProfile.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSLegalBusinessProfile(Partner.Profiles.LegalBusinessProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerLicenseDeploymentInfo.cs b/src/PowerShell/Commands/GetPartnerLicenseDeploymentInfo.cs index e39a724..75f53a7 100644 --- a/src/PowerShell/Commands/GetPartnerLicenseDeploymentInfo.cs +++ b/src/PowerShell/Commands/GetPartnerLicenseDeploymentInfo.cs @@ -20,7 +20,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection insights = Partner.Analytics.Licenses.Deployment.GetAsync().GetAwaiter().GetResult(); + ResourceCollection insights = Partner.Analytics.Licenses.Deployment.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(insights.Items.Select(l => new PSPartnerLicensesDeploymentInsight(l)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerLicenseUsageInfo.cs b/src/PowerShell/Commands/GetPartnerLicenseUsageInfo.cs index b316db4..bb70c5f 100644 --- a/src/PowerShell/Commands/GetPartnerLicenseUsageInfo.cs +++ b/src/PowerShell/Commands/GetPartnerLicenseUsageInfo.cs @@ -20,7 +20,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection insights = Partner.Analytics.Licenses.Usage.GetAsync().GetAwaiter().GetResult(); + ResourceCollection insights = Partner.Analytics.Licenses.Usage.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(insights.Items.Select(l => new PSPartnerLicensesUsageInsight(l)), true); } } diff --git a/src/PowerShell/Commands/GetPartnerMpnProfile.cs b/src/PowerShell/Commands/GetPartnerMpnProfile.cs index 58d9e21..94c564d 100644 --- a/src/PowerShell/Commands/GetPartnerMpnProfile.cs +++ b/src/PowerShell/Commands/GetPartnerMpnProfile.cs @@ -26,7 +26,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (string.IsNullOrEmpty(MpnId)) { - profile = Partner.Profiles.MpnProfile.GetAsync().GetAwaiter().GetResult(); + profile = Partner.Profiles.MpnProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } else { diff --git a/src/PowerShell/Commands/GetPartnerOffer.cs b/src/PowerShell/Commands/GetPartnerOffer.cs index 418cb6b..9e58c40 100644 --- a/src/PowerShell/Commands/GetPartnerOffer.cs +++ b/src/PowerShell/Commands/GetPartnerOffer.cs @@ -86,7 +86,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands countryCode.AssertNotEmpty(nameof(countryCode)); offerId.AssertNotEmpty(nameof(offerId)); - offer = Partner.Offers.ByCountry(countryCode).ById(offerId).GetAsync().GetAwaiter().GetResult(); + offer = Partner.Offers.ByCountry(countryCode).ById(offerId).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSOffer(offer)); } @@ -103,7 +103,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands countryCode.AssertNotEmpty(nameof(countryCode)); - offers = Partner.Offers.ByCountry(countryCode).GetAsync().GetAwaiter().GetResult(); + offers = Partner.Offers.ByCountry(countryCode).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteOutput(offers.Items); } @@ -119,7 +119,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands countryCode.AssertNotEmpty(nameof(countryCode)); category.AssertNotEmpty(nameof(category)); - offers = Partner.Offers.ByCountry(countryCode).ByCategory(category).GetAsync().GetAwaiter().GetResult(); + offers = Partner.Offers.ByCountry(countryCode).ByCategory(category).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteOutput(offers.Items); } diff --git a/src/PowerShell/Commands/GetPartnerOfferAddon.cs b/src/PowerShell/Commands/GetPartnerOfferAddon.cs index 2e5b681..190d80a 100644 --- a/src/PowerShell/Commands/GetPartnerOfferAddon.cs +++ b/src/PowerShell/Commands/GetPartnerOfferAddon.cs @@ -39,7 +39,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands ResourceCollection offers; string countryCode = (string.IsNullOrEmpty(CountryCode)) ? PartnerSession.Instance.Context.CountryCode : CountryCode; - offers = Partner.Offers.ByCountry(countryCode).ById(OfferId).AddOns.GetAsync().GetAwaiter().GetResult(); + offers = Partner.Offers.ByCountry(countryCode).ById(OfferId).AddOns.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(offers.Items.Select(o => new PSOffer(o)), true); } diff --git a/src/PowerShell/Commands/GetPartnerOfferCategory.cs b/src/PowerShell/Commands/GetPartnerOfferCategory.cs index b4640fe..5a2255a 100644 --- a/src/PowerShell/Commands/GetPartnerOfferCategory.cs +++ b/src/PowerShell/Commands/GetPartnerOfferCategory.cs @@ -27,7 +27,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ResourceCollection offerCategories = Partner.OfferCategories.ByCountry(CountryCode).GetAsync().GetAwaiter().GetResult(); + ResourceCollection offerCategories = Partner.OfferCategories.ByCountry(CountryCode).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(offerCategories.Items.Select(c => new PSOfferCategory(c))); } diff --git a/src/PowerShell/Commands/GetPartnerOrganizationProfile.cs b/src/PowerShell/Commands/GetPartnerOrganizationProfile.cs index 8073a47..f2e7a99 100644 --- a/src/PowerShell/Commands/GetPartnerOrganizationProfile.cs +++ b/src/PowerShell/Commands/GetPartnerOrganizationProfile.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSOrganizationProfile(Partner.Profiles.OrganizationProfile.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSOrganizationProfile(Partner.Profiles.OrganizationProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerProductAvailability.cs b/src/PowerShell/Commands/GetPartnerProductAvailability.cs index 021db6e..aacab85 100644 --- a/src/PowerShell/Commands/GetPartnerProductAvailability.cs +++ b/src/PowerShell/Commands/GetPartnerProductAvailability.cs @@ -92,7 +92,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands // If segment is specified, get the information using the segment. Otherwise don't if (!string.IsNullOrEmpty(segment)) { - productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.ByTargetSegment(segment).GetAsync().GetAwaiter().GetResult(); + productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.ByTargetSegment(segment).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (productAvailability.TotalCount > 0) { @@ -101,7 +101,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands } else { - productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.GetAsync().GetAwaiter().GetResult(); + productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (productAvailability.TotalCount > 0) { @@ -119,7 +119,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// Identifier for the product availability. private void GetProductAvailabilityById(string countryCode, string productId, string skuId, string availabilityId) { - Availability productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.ById(availabilityId).GetAsync().GetAwaiter().GetResult(); + Availability productAvailability = Partner.Products.ByCountry(countryCode).ById(productId).Skus.ById(skuId).Availabilities.ById(availabilityId).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (productAvailability != null) { diff --git a/src/PowerShell/Commands/GetPartnerResellerRequestLink.cs b/src/PowerShell/Commands/GetPartnerResellerRequestLink.cs index dbe5349..0fd8106 100644 --- a/src/PowerShell/Commands/GetPartnerResellerRequestLink.cs +++ b/src/PowerShell/Commands/GetPartnerResellerRequestLink.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSCustomerRelationshipRequest(Partner.Customers.RelationshipRequest.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSCustomerRelationshipRequest(Partner.Customers.RelationshipRequest.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerRole.cs b/src/PowerShell/Commands/GetPartnerRole.cs index 2d9955a..c9eb1f0 100644 --- a/src/PowerShell/Commands/GetPartnerRole.cs +++ b/src/PowerShell/Commands/GetPartnerRole.cs @@ -20,7 +20,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - SeekBasedResourceCollection roles = Partner.Roles.GetAsync().GetAwaiter().GetResult(); + SeekBasedResourceCollection roles = Partner.Roles.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(roles.Items.Select(r => new PSRole(r)), true); } diff --git a/src/PowerShell/Commands/GetPartnerRoleMember.cs b/src/PowerShell/Commands/GetPartnerRoleMember.cs index 0172681..b127129 100644 --- a/src/PowerShell/Commands/GetPartnerRoleMember.cs +++ b/src/PowerShell/Commands/GetPartnerRoleMember.cs @@ -28,7 +28,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - SeekBasedResourceCollection members = Partner.Roles[RoleId].Members.GetAsync().GetAwaiter().GetResult(); + SeekBasedResourceCollection members = Partner.Roles[RoleId].Members.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(members.Items.Select(m => new PSUserMember(m)), true); } diff --git a/src/PowerShell/Commands/GetPartnerServiceIncident.cs b/src/PowerShell/Commands/GetPartnerServiceIncident.cs index d3a0649..494b816 100644 --- a/src/PowerShell/Commands/GetPartnerServiceIncident.cs +++ b/src/PowerShell/Commands/GetPartnerServiceIncident.cs @@ -36,7 +36,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands ResourceCollection incidents; IEnumerable results; - incidents = Partner.ServiceIncidents.GetAsync().GetAwaiter().GetResult(); + incidents = Partner.ServiceIncidents.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (incidents.TotalCount > 0) { diff --git a/src/PowerShell/Commands/GetPartnerServiceRequest.cs b/src/PowerShell/Commands/GetPartnerServiceRequest.cs index ab6a3ac..e568bf2 100644 --- a/src/PowerShell/Commands/GetPartnerServiceRequest.cs +++ b/src/PowerShell/Commands/GetPartnerServiceRequest.cs @@ -99,7 +99,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands requestId.AssertNotEmpty(nameof(requestId)); - request = Partner.Customers.ById(customerId).ServiceRequests.ById(requestId).GetAsync().GetAwaiter().GetResult(); + request = Partner.Customers.ById(customerId).ServiceRequests.ById(requestId).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (request != null) { @@ -123,7 +123,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); - requests = Partner.Customers.ById(customerId).ServiceRequests.GetAsync().GetAwaiter().GetResult(); + requests = Partner.Customers.ById(customerId).ServiceRequests.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (requests.TotalCount > 0) { @@ -144,7 +144,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands requestId.AssertNotEmpty(nameof(requestId)); - request = Partner.ServiceRequests.ById(requestId).GetAsync().GetAwaiter().GetResult(); + request = Partner.ServiceRequests.ById(requestId).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (request != null) { @@ -162,7 +162,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { ResourceCollection requests; - requests = Partner.ServiceRequests.GetAsync().GetAwaiter().GetResult(); + requests = Partner.ServiceRequests.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (requests.TotalCount > 0) { @@ -181,7 +181,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands while (enumerator.HasValue) { serviceRequests.AddRange(enumerator.Current.Items); - enumerator.NextAsync().GetAwaiter().GetResult(); + enumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } if (severity.HasValue && status.HasValue) diff --git a/src/PowerShell/Commands/GetPartnerServiceRequestTopic.cs b/src/PowerShell/Commands/GetPartnerServiceRequestTopic.cs index 433c27c..85838cd 100644 --- a/src/PowerShell/Commands/GetPartnerServiceRequestTopic.cs +++ b/src/PowerShell/Commands/GetPartnerServiceRequestTopic.cs @@ -31,7 +31,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands ResourceCollection topics; IEnumerable results; - topics = Partner.ServiceRequests.SupportTopics.GetAsync().GetAwaiter().GetResult(); + topics = Partner.ServiceRequests.SupportTopics.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (topics.TotalCount > 0) { diff --git a/src/PowerShell/Commands/GetPartnerSupportProfile.cs b/src/PowerShell/Commands/GetPartnerSupportProfile.cs index 175e540..7db5bdc 100644 --- a/src/PowerShell/Commands/GetPartnerSupportProfile.cs +++ b/src/PowerShell/Commands/GetPartnerSupportProfile.cs @@ -17,7 +17,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(new PSSupportProfile(Partner.Profiles.SupportProfile.GetAsync().GetAwaiter().GetResult())); + WriteObject(new PSSupportProfile(Partner.Profiles.SupportProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult())); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/GetPartnerUser.cs b/src/PowerShell/Commands/GetPartnerUser.cs index 432b541..7ec918e 100644 --- a/src/PowerShell/Commands/GetPartnerUser.cs +++ b/src/PowerShell/Commands/GetPartnerUser.cs @@ -9,12 +9,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Graph; using Models.Authentication; using Network; + using Properties; /// /// Command that gets partner level user accounts. /// [Cmdlet(VerbsCommon.Get, "PartnerUser"), OutputType(typeof(User))] - public class GetPartnerUser : PartnerCmdlet + public class GetPartnerUser : PartnerAsyncCmdlet { /// /// Gets or sets the user identifier. @@ -29,22 +30,38 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands [Alias("UPN")] public string UserPrincipalName { get; set; } + /// + /// Operations that happen before the cmdlet is executed. + /// + protected override void BeginProcessing() + { + if (PartnerSession.Instance.Context == null) + { + throw new PSInvalidOperationException(Resources.RunConnectPartnerCenter); + } + + base.BeginProcessing(); + } + /// /// Executes the operations associated with the cmdlet. /// public override void ExecuteCmdlet() { - GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as GraphServiceClient; - client.AuthenticationProvider = new GraphAuthenticationProvider(); + Scheduler.RunTask(async () => + { + GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as GraphServiceClient; + client.AuthenticationProvider = new GraphAuthenticationProvider(); - if (string.IsNullOrEmpty(UserId) && string.IsNullOrEmpty(UserPrincipalName)) - { - WriteObject(GetUsersAsync(client).ConfigureAwait(false).GetAwaiter().GetResult(), true); - } - else - { - WriteObject(client.Users[string.IsNullOrEmpty(UserPrincipalName) ? UserId : UserPrincipalName].Request().GetAsync().ConfigureAwait(false).GetAwaiter().GetResult()); - } + if (string.IsNullOrEmpty(UserId) && string.IsNullOrEmpty(UserPrincipalName)) + { + WriteObject(await GetUsersAsync(client).ConfigureAwait(false), true); + } + else + { + WriteObject(await client.Users[string.IsNullOrEmpty(UserPrincipalName) ? UserId : UserPrincipalName].Request().GetAsync().ConfigureAwait(false)); + } + }); } private async Task> GetUsersAsync(IGraphServiceClient client) diff --git a/src/PowerShell/Commands/GetPartnerUserSignInActivity.cs b/src/PowerShell/Commands/GetPartnerUserSignInActivity.cs index 126fbf4..f10f007 100644 --- a/src/PowerShell/Commands/GetPartnerUserSignInActivity.cs +++ b/src/PowerShell/Commands/GetPartnerUserSignInActivity.cs @@ -7,13 +7,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using System.Collections.Generic; using System.Management.Automation; using System.Text.RegularExpressions; - using System.Threading.Tasks; using Graph; using Models.Authentication; using Network; + using Properties; [Cmdlet(VerbsCommon.Get, "PartnerUserSignInActivity"), OutputType(typeof(SignIn))] - public class GetPartnerUserSignInActivity : PartnerCmdlet + public class GetPartnerUserSignInActivity : PartnerAsyncCmdlet { /// /// Gets or sets the end date porition of the query. @@ -34,12 +34,24 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands [ValidatePattern(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", Options = RegexOptions.Compiled | RegexOptions.IgnoreCase)] public string UserId { get; set; } + /// + /// Operations that happen before the cmdlet is executed. + /// + protected override void BeginProcessing() + { + if (PartnerSession.Instance.Context == null) + { + throw new PSInvalidOperationException(Resources.RunConnectPartnerCenter); + } + + base.BeginProcessing(); + } + /// /// Executes the operations associated with the cmdlet. /// public override void ExecuteCmdlet() { - List activities; string filter = string.Empty; if (StartDate != null) @@ -57,39 +69,35 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands filter = AppendValue(filter, $"userId eq '{UserId}'"); } - activities = GetSignInActivitiesAsync(filter).ConfigureAwait(false).GetAwaiter().GetResult(); - - WriteObject(activities, true); - } - - private async Task> GetSignInActivitiesAsync(string filter) - { - List activities; - List queryOptions = null; - - if (!string.IsNullOrEmpty(filter)) + Scheduler.RunTask(async () => { - queryOptions = new List + List activities; + List queryOptions = null; + + if (!string.IsNullOrEmpty(filter)) { - new QueryOption("$filter", $"({filter})") - }; - } + queryOptions = new List + { + new QueryOption("$filter", $"({filter})") + }; + } - GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as GraphServiceClient; - client.AuthenticationProvider = new GraphAuthenticationProvider(); + GraphServiceClient client = PartnerSession.Instance.ClientFactory.CreateGraphServiceClient() as GraphServiceClient; + client.AuthenticationProvider = new GraphAuthenticationProvider(); - IAuditLogRootSignInsCollectionPage data = await client - .AuditLogs.SignIns.Request(queryOptions).GetAsync(CancellationToken).ConfigureAwait(false); + IAuditLogRootSignInsCollectionPage data = await client + .AuditLogs.SignIns.Request(queryOptions).GetAsync(CancellationToken).ConfigureAwait(false); - activities = new List(data.CurrentPage); + activities = new List(data.CurrentPage); - while (data.NextPageRequest != null) - { - data = await data.NextPageRequest.GetAsync(CancellationToken).ConfigureAwait(false); - activities.AddRange(data.CurrentPage); - } + while (data.NextPageRequest != null) + { + data = await data.NextPageRequest.GetAsync(CancellationToken).ConfigureAwait(false); + activities.AddRange(data.CurrentPage); + } - return activities; + WriteObject(activities, true); + }); } private static string AppendValue(string baseValue, string appendValue) diff --git a/src/PowerShell/Commands/GetPartnerValidationCode.cs b/src/PowerShell/Commands/GetPartnerValidationCode.cs index 5a5c176..76a2fb8 100644 --- a/src/PowerShell/Commands/GetPartnerValidationCode.cs +++ b/src/PowerShell/Commands/GetPartnerValidationCode.cs @@ -19,7 +19,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(Partner.Validations.GetValidationCodesAsync().GetAwaiter().GetResult().Select(c => new PSValidationCode(c)), true); + WriteObject(Partner.Validations.GetValidationCodesAsync().ConfigureAwait(false).GetAwaiter().GetResult().Select(c => new PSValidationCode(c)), true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/NewPartnerAccessToken.cs b/src/PowerShell/Commands/NewPartnerAccessToken.cs index d3c7642..dfaa456 100644 --- a/src/PowerShell/Commands/NewPartnerAccessToken.cs +++ b/src/PowerShell/Commands/NewPartnerAccessToken.cs @@ -9,20 +9,25 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using System.Management.Automation; using System.Text; using Extensions; - using Factories; using Identity.Client; + using Microsoft.Store.PartnerCenter.PowerShell.Factories; using Models.Authentication; using Newtonsoft.Json.Linq; [Cmdlet(VerbsCommon.New, "PartnerAccessToken")] [OutputType(typeof(AuthResult))] - public class NewPartnerAccessToken : PartnerPSCmdlet + public class NewPartnerAccessToken : PartnerAsyncCmdlet { /// /// The name of the access token parameter set. /// private const string AccessTokenParameterSet = "AccessToken"; + /// + /// The name of the by module parameter set. + /// + private const string ByModuleParameterSet = "ByModule"; + /// /// The message written to the console. /// @@ -53,7 +58,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets or sets the application identifier. /// - [Parameter(HelpMessage = "The application identifier to be used during authentication.", Mandatory = true)] + [Parameter(HelpMessage = "The application identifier to be used during authentication.", Mandatory = true, ParameterSetName = AccessTokenParameterSet)] + [Parameter(HelpMessage = "The application identifier to be used during authentication.", Mandatory = true, ParameterSetName = ServicePrincipalParameterSet)] + [Parameter(HelpMessage = "The application identifier to be used during authentication.", Mandatory = true, ParameterSetName = ServicePrincipalCertificateParameterSet)] + [Parameter(HelpMessage = "The application identifier to be used during authentication.", Mandatory = true, ParameterSetName = UserParameterSet)] [Alias("ClientId")] [ValidateNotNullOrEmpty] public string ApplicationId { get; set; } @@ -61,7 +69,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets or sets the certificate thumbprint. /// - [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = true, HelpMessage = "Certificate Hash (Thumbprint)")] + [Parameter(HelpMessage = "Certificate Hash (Thumbprint)", Mandatory = true, ParameterSetName = ServicePrincipalCertificateParameterSet)] public string CertificateThumbprint { get; set; } /// @@ -80,6 +88,14 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands [ValidateNotNullOrEmpty] public EnvironmentName Environment { get; set; } + /// + /// Gets or sets the module that an access token is being generated. + /// + [Parameter(HelpMessage = "The module that an access token is being generated.", Mandatory = true, ParameterSetName = ByModuleParameterSet)] + [Alias("ModuleName")] + [ValidateSet(nameof(ModuleName.ExchangeOnline))] + public ModuleName Module { get; set; } + /// /// Gets or sets the refresh token to use during authentication. /// @@ -90,7 +106,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets or sets the scopes used for authentication. /// - [Parameter(HelpMessage = "Scopes requested to access a protected API.", Mandatory = true)] + [Parameter(HelpMessage = "Scopes requested to access a protected API.", Mandatory = true, ParameterSetName = AccessTokenParameterSet)] + [Parameter(HelpMessage = "Scopes requested to access a protected API.", Mandatory = true, ParameterSetName = ServicePrincipalParameterSet)] + [Parameter(HelpMessage = "Scopes requested to access a protected API.", Mandatory = true, ParameterSetName = ServicePrincipalCertificateParameterSet)] + [Parameter(HelpMessage = "Scopes requested to access a protected API.", Mandatory = false, ParameterSetName = UserParameterSet)] public string[] Scopes { get; set; } /// @@ -105,6 +124,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// [Alias("Domain", "TenantId")] [Parameter(HelpMessage = "Identifier or name for the tenant.", Mandatory = false, ParameterSetName = AccessTokenParameterSet)] + [Parameter(HelpMessage = "Identifier or name for the tenant.", Mandatory = false, ParameterSetName = ByModuleParameterSet)] [Parameter(HelpMessage = "Identifier or name for the tenant.", Mandatory = true, ParameterSetName = ServicePrincipalCertificateParameterSet)] [Parameter(HelpMessage = "Identifier or name for the tenant.", Mandatory = true, ParameterSetName = ServicePrincipalParameterSet)] [Parameter(HelpMessage = "Identifier or name for the tenant.", Mandatory = false, ParameterSetName = UserParameterSet)] @@ -122,126 +142,139 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// Gets or sets a flag indicating if the device code flow should be used. /// [Alias("DeviceCode", "DeviceAuth", "Device")] - [Parameter(ParameterSetName = UserParameterSet, Mandatory = false, HelpMessage = "Use device code authentication instead of a browser control")] + [Parameter(ParameterSetName = UserParameterSet, Mandatory = false, HelpMessage = "Use device code authentication instead of a browser control.")] public SwitchParameter UseDeviceAuthentication { get; set; } /// - /// Performs the execution of the command. + /// Executes the operations associated with the cmdlet. /// - protected override void ProcessRecord() + public override void ExecuteCmdlet() { - PartnerAccount account = new PartnerAccount(); - - if (ParameterSetName.Equals(AccessTokenParameterSet, StringComparison.InvariantCultureIgnoreCase)) + Scheduler.RunTask(async () => { - account.SetProperty(PartnerAccountPropertyType.AccessToken, AccessToken); - account.Type = AccountType.AccessToken; - } - else if (ParameterSetName.Equals(ServicePrincipalParameterSet, StringComparison.InvariantCultureIgnoreCase)) - { - account.ObjectId = Credential.UserName; - account.SetProperty(PartnerAccountPropertyType.ServicePrincipalSecret, Credential.Password.ConvertToString()); - account.Type = AccountType.ServicePrincipal; - } - else - { - account.Type = AccountType.User; - } + PartnerAccount account = new PartnerAccount(); + string applicationId; - if (UseAuthorizationCode.IsPresent) - { - account.SetProperty("UseAuthCode", "true"); - } - - if (UseDeviceAuthentication.IsPresent) - { - account.SetProperty("UseDeviceAuth", "true"); - } - - if (!string.IsNullOrEmpty(RefreshToken)) - { - account.SetProperty(PartnerAccountPropertyType.RefreshToken, RefreshToken); - } - - account.SetProperty(PartnerAccountPropertyType.ApplicationId, ApplicationId); - - account.Tenant = string.IsNullOrEmpty(Tenant) ? "common" : Tenant; - - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( - account, - PartnerEnvironment.PublicEnvironments[Environment], - Scopes, - Message, - WriteWarning, - WriteDebug, - CancellationToken); - - byte[] cacheData = SharedTokenCacheClientFactory.GetTokenCache(ApplicationId).SerializeMsalV3(); - - IEnumerable knownPropertyNames = new[] { "AccessToken", "RefreshToken", "IdToken", "Account", "AppMetadata" }; - - JObject root = JObject.Parse(Encoding.UTF8.GetString(cacheData, 0, cacheData.Length)); - - IDictionary known = (root as IDictionary) - .Where(kvp => knownPropertyNames.Any(p => string.Equals(kvp.Key, p, StringComparison.OrdinalIgnoreCase))) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - IDictionary tokens = new Dictionary(); - - if (known.ContainsKey("RefreshToken")) - { - foreach (JToken token in root["RefreshToken"].Values()) + if (ParameterSetName.Equals(AccessTokenParameterSet, StringComparison.InvariantCultureIgnoreCase)) { - if (token is JObject j) - { - TokenCacheItem item = new TokenCacheItem - { - ClientId = ExtractExistingOrEmptyString(j, "client_id"), - CredentialType = ExtractExistingOrEmptyString(j, "credential_type"), - Environment = ExtractExistingOrEmptyString(j, "environment"), - HomeAccountId = ExtractExistingOrEmptyString(j, "home_account_id"), - RawClientInfo = ExtractExistingOrEmptyString(j, "client_info"), - Secret = ExtractExistingOrEmptyString(j, "secret") - }; + account.SetProperty(PartnerAccountPropertyType.AccessToken, AccessToken); + account.Type = AccountType.AccessToken; + applicationId = ApplicationId; + } + else if (ParameterSetName.Equals(ByModuleParameterSet, StringComparison.InvariantCultureIgnoreCase)) + { + account.SetProperty(PartnerAccountPropertyType.UseDeviceAuth, "true"); + account.Type = AccountType.User; + applicationId = PowerShellModule.KnownModules[Module].ApplicationId; - tokens.Add($"{item.HomeAccountId}-{item.Environment}-RefreshToken-{item.ClientId}--", item); + Scopes = PowerShellModule.KnownModules[Module].Scopes.ToArray(); + } + else if (ParameterSetName.Equals(ServicePrincipalParameterSet, StringComparison.InvariantCultureIgnoreCase)) + { + account.ObjectId = Credential.UserName; + account.SetProperty(PartnerAccountPropertyType.ServicePrincipalSecret, Credential.Password.ConvertToString()); + account.Type = AccountType.ServicePrincipal; + applicationId = ApplicationId; + } + else + { + account.Type = AccountType.User; + applicationId = ApplicationId; + } + + if (!ParameterSetName.Equals(ByModuleParameterSet, StringComparison.InvariantCultureIgnoreCase)) + { + if (UseAuthorizationCode.IsPresent) + { + account.SetProperty(PartnerAccountPropertyType.UseAuthCode, "true"); + } + + if (UseDeviceAuthentication.IsPresent) + { + account.SetProperty(PartnerAccountPropertyType.UseDeviceAuth, "true"); } } - } - string key = GetTokenCacheKey(authResult); + if (!string.IsNullOrEmpty(RefreshToken)) + { + account.SetProperty(PartnerAccountPropertyType.RefreshToken, RefreshToken); + } - AuthResult result = new AuthResult( - authResult.AccessToken, - authResult.IsExtendedLifeTimeToken, - authResult.UniqueId, - authResult.ExpiresOn, - authResult.ExtendedExpiresOn, - authResult.TenantId, - authResult.Account, - authResult.IdToken, - authResult.Scopes); + account.SetProperty(PartnerAccountPropertyType.ApplicationId, applicationId); + account.Tenant = string.IsNullOrEmpty(Tenant) ? "organizations" : Tenant; - if (tokens.ContainsKey(key)) - { - result.RefreshToken = tokens[key].Secret; - } + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( + account, + PartnerEnvironment.PublicEnvironments[Environment], + Scopes, + Message, + CancellationToken).ConfigureAwait(false); - WriteObject(result); - } + byte[] cacheData = SharedTokenCacheClientFactory.GetMsalCacheStorage(ApplicationId).ReadData(); - private string GetTokenCacheKey(AuthenticationResult authResult) - { - return $"{authResult.Account.HomeAccountId.Identifier}-{authResult.Account.Environment}-RefreshToken-{ApplicationId}--"; + IEnumerable knownPropertyNames = new[] { "AccessToken", "RefreshToken", "IdToken", "Account", "AppMetadata" }; + + JObject root = JObject.Parse(Encoding.UTF8.GetString(cacheData, 0, cacheData.Length)); + + IDictionary known = (root as IDictionary) + .Where(kvp => knownPropertyNames.Any(p => string.Equals(kvp.Key, p, StringComparison.OrdinalIgnoreCase))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + IDictionary tokens = new Dictionary(); + + if (known.ContainsKey("RefreshToken")) + { + foreach (JToken token in root["RefreshToken"].Values()) + { + if (token is JObject j) + { + TokenCacheItem item = new TokenCacheItem + { + ClientId = ExtractExistingOrEmptyString(j, "client_id"), + CredentialType = ExtractExistingOrEmptyString(j, "credential_type"), + Environment = ExtractExistingOrEmptyString(j, "environment"), + HomeAccountId = ExtractExistingOrEmptyString(j, "home_account_id"), + RawClientInfo = ExtractExistingOrEmptyString(j, "client_info"), + Secret = ExtractExistingOrEmptyString(j, "secret") + }; + + tokens.Add($"{item.HomeAccountId}-{item.Environment}-RefreshToken-{item.ClientId}--", item); + } + } + } + + AuthResult result = new AuthResult( + authResult.AccessToken, + authResult.IsExtendedLifeTimeToken, + authResult.UniqueId, + authResult.ExpiresOn, + authResult.ExtendedExpiresOn, + authResult.TenantId, + authResult.Account, + authResult.IdToken, + authResult.Scopes, + authResult.CorrelationId); + + if (authResult.Account != null) + { + string key = SharedTokenCacheClientFactory.GetTokenCacheKey(authResult, applicationId); + + if (tokens.ContainsKey(key)) + { + result.RefreshToken = tokens[key].Secret; + } + } + + WriteObject(result); + }); } private static string ExtractExistingOrEmptyString(JObject json, string key) { if (json.TryGetValue(key, out JToken val)) { - string strVal = val.ToObject(); json.Remove(key); - return strVal; + return val.ToObject(); ; } return string.Empty; diff --git a/src/PowerShell/Commands/NewPartnerAzureSubscription.cs b/src/PowerShell/Commands/NewPartnerAzureSubscription.cs index 0ef532f..3a656c4 100644 --- a/src/PowerShell/Commands/NewPartnerAzureSubscription.cs +++ b/src/PowerShell/Commands/NewPartnerAzureSubscription.cs @@ -3,14 +3,27 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { + using System; using System.Management.Automation; + using System.Text.RegularExpressions; using Azure.Management.Profiles.Subscription; using Azure.Management.Profiles.Subscription.Models; using Models.Authentication; - [Cmdlet(VerbsCommon.New, "PartnerAzureSubscription"), OutputType(typeof(SubscriptionCreationResult))] - public class NewPartnerAzureSubscription : PartnerPSCmdlet + [Cmdlet(VerbsCommon.New, "PartnerAzureSubscription", DefaultParameterSetName = ByCustomerNameParameterSet)] + [OutputType(typeof(SubscriptionCreationResult))] + public class NewPartnerAzureSubscription : PartnerAsyncCmdlet { + /// + /// Name of the by customer identifier parameter set. + /// + private const string ByCustomerIdParameterSet = "ByCustomerId"; + + /// + /// Name of the by customer name parameter set. + /// + private const string ByCustomerNameParameterSet = "ByCustomerName"; + /// /// Gets or sets the name for the billing account. /// @@ -20,9 +33,17 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets or sets the name for the customer. /// - [Parameter(HelpMessage = "The name for the customer.", Mandatory = true)] + [BreakingChange("Replacing the customer name parameter with the customer identifier parameter.", "3.0.1", NewWay = "New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerId '1e5a6ab0-e5ef-4f4e-a208-399e792b5ed4' -DisplayName 'Microsoft Azure'", OldWay = "New-PartnerAzureSubscription -BillingAccountName '99a13315-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_xxxx-xx-xx' -CustomerName 'Contoso' -DisplayName 'Microsoft Azure'")] + [Parameter(HelpMessage = "The name of the customer.", Mandatory = true, ParameterSetName = ByCustomerNameParameterSet)] public string CustomerName { get; set; } + /// + /// Gets or sets the identifier for the customer. + /// + [Parameter(HelpMessage = "The identifier for the customer.", Mandatory = true, ParameterSetName = ByCustomerIdParameterSet)] + [ValidatePattern(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", Options = RegexOptions.Compiled | RegexOptions.IgnoreCase)] + public string CustomerId { get; set; } + /// /// Gets or sets the display name for the subscription. /// @@ -40,15 +61,25 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ISubscriptionClient client = PartnerSession.Instance.ClientFactory.CreateServiceClient(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); - ModernCspSubscriptionCreationParameters parameters = new ModernCspSubscriptionCreationParameters + Scheduler.RunTask(async () => { - DisplayName = DisplayName, - ResellerId = ResellerId ?? null, - SkuId = "0001" - }; + ISubscriptionClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); + ModernCspSubscriptionCreationParameters parameters = new ModernCspSubscriptionCreationParameters + { + DisplayName = DisplayName, + ResellerId = ResellerId ?? null, + SkuId = "0001" + }; - WriteObject(client.SubscriptionFactory.CreateCspSubscriptionAsync(BillingAccountName, CustomerName, parameters, CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult()); + if (ParameterSetName.Equals(ByCustomerIdParameterSet, StringComparison.InvariantCultureIgnoreCase)) + { + WriteObject(await client.SubscriptionFactory.CreateCspSubscriptionAsync(BillingAccountName, CustomerId, parameters, CancellationToken).ConfigureAwait(false)); + } + else + { + WriteObject(await client.SubscriptionFactory.CreateCspSubscriptionAsync(BillingAccountName, CustomerName, parameters, CancellationToken).ConfigureAwait(false)); + } + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/NewPartnerCustomer.cs b/src/PowerShell/Commands/NewPartnerCustomer.cs index ef277cf..a69c87e 100644 --- a/src/PowerShell/Commands/NewPartnerCustomer.cs +++ b/src/PowerShell/Commands/NewPartnerCustomer.cs @@ -200,7 +200,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (!DisableValidation.ToBool()) { - if (Partner.Domains.ByDomain(Domain).ExistsAsync().GetAwaiter().GetResult()) + if (Partner.Domains.ByDomain(Domain).ExistsAsync().ConfigureAwait(false).GetAwaiter().GetResult()) { throw new PSInvalidOperationException( string.Format( diff --git a/src/PowerShell/Commands/NewPartnerCustomerConfigurationPolicy.cs b/src/PowerShell/Commands/NewPartnerCustomerConfigurationPolicy.cs index 160871b..799a2fc 100644 --- a/src/PowerShell/Commands/NewPartnerCustomerConfigurationPolicy.cs +++ b/src/PowerShell/Commands/NewPartnerCustomerConfigurationPolicy.cs @@ -13,7 +13,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Creates a new configuration policies for the specified customer identifier. /// - [Cmdlet(VerbsCommon.New, "PartnerCustomerConfigurationPolicy", SupportsShouldProcess = true), OutputType(typeof(PSConfigurationPolicy))] + [Cmdlet(VerbsCommon.New, "PartnerCustomerConfigurationPolicy", SupportsShouldProcess = true)] + [OutputType(typeof(PSConfigurationPolicy))] public class NewPartnerCustomerConfigurationPolicy : PartnerCmdlet { /// diff --git a/src/PowerShell/Commands/NewPartnerCustomerDeviceBatch.cs b/src/PowerShell/Commands/NewPartnerCustomerDeviceBatch.cs index fd95e8f..d9cd92d 100644 --- a/src/PowerShell/Commands/NewPartnerCustomerDeviceBatch.cs +++ b/src/PowerShell/Commands/NewPartnerCustomerDeviceBatch.cs @@ -90,13 +90,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands location = Partner.Customers[CustomerId].DeviceBatches.CreateAsync(request).GetAwaiter().GetResult(); } - status = Partner.Customers[CustomerId].BatchUploadStatus.ById(location.Split('/')[4]).GetAsync().GetAwaiter().GetResult(); + status = Partner.Customers[CustomerId].BatchUploadStatus.ById(location.Split('/')[4]).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); while (status.Status == DeviceUploadStatusType.Processing || status.Status == DeviceUploadStatusType.Queued) { Thread.Sleep(5000); - status = Partner.Customers[CustomerId].BatchUploadStatus.ById(location.Split('/')[4]).GetAsync().GetAwaiter().GetResult(); + status = Partner.Customers[CustomerId].BatchUploadStatus.ById(location.Split('/')[4]).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } WriteObject(new PSBatchUploadDetails(status)); diff --git a/src/PowerShell/Commands/PartnerAsyncCmdlet.cs b/src/PowerShell/Commands/PartnerAsyncCmdlet.cs new file mode 100644 index 0000000..ea0cc3c --- /dev/null +++ b/src/PowerShell/Commands/PartnerAsyncCmdlet.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Commands +{ + using System; + using System.Collections.Concurrent; + using System.Management.Automation; + using System.Threading.Tasks; + using Models; + using Models.Authentication; + using Utilities; + + /// + /// The base class for asynchronous partner cmdlets. + /// + public abstract class PartnerAsyncCmdlet : PartnerPSCmdlet + { + /// + /// Key for the write debug component. + /// + private const string WriteDebugKey = "WriteDebug"; + + /// + /// Key for the write error component. + /// + private const string WriteErrorKey = "WriteError"; + + /// + /// Key for the write object component. + /// + private const string WriteObjectKey = "WriteObject"; + + /// + /// Key for the write warning component. + /// + private const string WriteWarningKey = "WriteWarning"; + + /// + /// The event that is triggered when the write debug operation is invoked. + /// + private event EventHandler OnWriteDebug; + + /// + /// The event that is triggered when the write error operation is invoked. + /// + private event EventHandler OnWriteError; + + /// + /// The event that is triggered when the write object operation is invoked. + /// + private event EventHandler OnWriteObject; + + /// + /// The event that is triggered when the write warning operation is invoked. + /// + private event EventHandler OnWriteWarning; + + /// + /// The queue of output tasks to be invoked. + /// + private readonly ConcurrentQueue outputTasks = new ConcurrentQueue(); + + /// + /// Gets the scheduler used for task execution. + /// + public ConcurrencyTaskScheduler Scheduler { get; private set; } + + /// + /// Operations that happen before the cmdlet is invoked. + /// + protected override void BeginProcessing() + { + base.BeginProcessing(); + + Scheduler = new ConcurrencyTaskScheduler(1000, CancellationToken); + + Scheduler.OnError += Scheduler_OnError; + + OnWriteDebug -= Cmdlet_OnWriteDebug; + OnWriteDebug += Cmdlet_OnWriteDebug; + + OnWriteError -= Cmdlet_OnWriteError; + OnWriteError += Cmdlet_OnWriteError; + + OnWriteObject -= Cmdlet_OnWriteObject; + OnWriteObject += Cmdlet_OnWriteObject; + + OnWriteWarning -= Cmdlet_OnWriteWarning; + OnWriteWarning += Cmdlet_OnWriteWarning; + + PartnerSession.Instance.UnregisterComponent>(WriteDebugKey); + PartnerSession.Instance.UnregisterComponent>(WriteErrorKey); + PartnerSession.Instance.UnregisterComponent>(WriteObjectKey); + PartnerSession.Instance.UnregisterComponent>(WriteWarningKey); + + PartnerSession.Instance.RegisterComponent(WriteDebugKey, () => OnWriteDebug); + PartnerSession.Instance.RegisterComponent(WriteErrorKey, () => OnWriteError); + PartnerSession.Instance.RegisterComponent(WriteObjectKey, () => OnWriteObject); + PartnerSession.Instance.RegisterComponent(WriteWarningKey, () => OnWriteWarning); + } + + /// + /// Operations that happend after the cmdlet is invoked. + /// + protected override void EndProcessing() + { + try + { + do + { + while (outputTasks.TryDequeue(out Task task)) + { + task?.RunSynchronously(); + } + } + while (!Scheduler.CheckForComplete(500, CancellationToken)); + + if (!outputTasks.IsEmpty) + { + while (outputTasks.TryDequeue(out Task task)) + { + task?.RunSynchronously(); + } + } + } + finally + { + Scheduler?.Dispose(); + } + + base.EndProcessing(); + } + + /// + /// Writes the debug information. + /// + /// The text to be written to the pipeline. + protected new void WriteDebug(string text) + { + if (PartnerSession.Instance.TryGetComponent(WriteDebugKey, out EventHandler writeObjectEvent)) + { + writeObjectEvent(this, new StreamEventArgs { Resource = text }); + } + } + + /// + /// Writes the error record to the pipeline. + /// + /// The error record to be written to the pipeline. + protected new void WriteError(ErrorRecord errorRecord) + { + if (PartnerSession.Instance.TryGetComponent(WriteDebugKey, out EventHandler writeObjectEvent)) + { + writeObjectEvent(this, new StreamEventArgs { Resource = errorRecord }); + } + } + + /// + /// Writes the object the pipeline. + /// + /// The object to be written to the pipeline. + protected new void WriteObject(object sendToPipeline) + { + if (PartnerSession.Instance.TryGetComponent(WriteObjectKey, out EventHandler writeObjectEvent)) + { + writeObjectEvent(this, new StreamEventArgs { EnumerateCollection = false, Resource = sendToPipeline }); + } + } + + /// + /// Writes the object the pipeline. + /// + /// The object to be written to the pipeline. + /// A flag indicating whether or not to enumerate the collection. + protected new void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (PartnerSession.Instance.TryGetComponent(WriteObjectKey, out EventHandler writeObjectEvent)) + { + writeObjectEvent(this, new StreamEventArgs { EnumerateCollection = enumerateCollection, Resource = sendToPipeline }); + } + } + + /// + /// Writes the warning message to the pipeline. + /// + /// The text to be written to the pipeline. + protected new void WriteWarning(string text) + { + if (PartnerSession.Instance.TryGetComponent(WriteWarningKey, out EventHandler writeWarningEvent)) + { + writeWarningEvent(this, new StreamEventArgs() { Resource = text }); + } + } + + private void Cmdlet_OnWriteDebug(object sender, StreamEventArgs args) + { + outputTasks.Enqueue(new Task(() => base.WriteDebug(args.Resource.ToString()))); + } + + private void Cmdlet_OnWriteError(object sender, StreamEventArgs e) + { + outputTasks.Enqueue(new Task(() => base.WriteExceptionError(e.Resource as Exception))); + } + + private void Cmdlet_OnWriteObject(object sender, StreamEventArgs args) + { + outputTasks.Enqueue(new Task(() => base.WriteObject(args.Resource, args.EnumerateCollection))); + } + + private void Cmdlet_OnWriteWarning(object sender, StreamEventArgs args) + { + outputTasks.Enqueue(new Task(() => base.WriteWarning(args.Resource.ToString()))); + } + + private void Scheduler_OnError(object sender, TaskExceptionEventArgs e) + { + if (PartnerSession.Instance.TryGetComponent(WriteErrorKey, out EventHandler writeErrorEvent)) + { + writeErrorEvent(this, new StreamEventArgs() { Resource = e.Exception }); + } + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Commands/PartnerCmdlet.cs b/src/PowerShell/Commands/PartnerCmdlet.cs index 4b12346..3d911df 100644 --- a/src/PowerShell/Commands/PartnerCmdlet.cs +++ b/src/PowerShell/Commands/PartnerCmdlet.cs @@ -8,7 +8,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Properties; /// - /// Represents base class for the Partner Center cmdlets. + /// The base class for the partner cmdlets. /// public abstract class PartnerCmdlet : PartnerPSCmdlet { diff --git a/src/PowerShell/Commands/PartnerPSCmdlet.cs b/src/PowerShell/Commands/PartnerPSCmdlet.cs index 45b9a55..5370d4d 100644 --- a/src/PowerShell/Commands/PartnerPSCmdlet.cs +++ b/src/PowerShell/Commands/PartnerPSCmdlet.cs @@ -15,6 +15,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using System.Threading; using ApplicationInsights; using ApplicationInsights.DataContracts; + using ApplicationInsights.Extensibility; using Exceptions; using Models; using Models.Authentication; @@ -23,7 +24,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Rest; /// - /// Represents base class for the Partner Center PowerShell cmdlets. + /// The base class for Partner Center PowerShell cmdlets. /// public abstract class PartnerPSCmdlet : PSCmdlet { @@ -40,11 +41,16 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Client that provides the ability to interact with the Application Insights service. /// - private static readonly TelemetryClient telemetryClient = new TelemetryClient + private static readonly TelemetryClient telemetryClient = new TelemetryClient(TelemetryConfiguration.CreateDefault()) { InstrumentationKey = "786d393c-be8e-46a8-b2b2-a3b6d5b417fc" }; + /// + /// Lock used to synchronize mutation of the tracing interceptors. + /// + private readonly object resourceLock = new object(); + /// /// Provides a signal to that it should be canceled. /// @@ -77,14 +83,26 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { get { - if (string.IsNullOrEmpty(hashMacAddress)) + lock (resourceLock) { - string value = NetworkInterface.GetAllNetworkInterfaces()? - .FirstOrDefault(nic => nic != null && - nic.OperationalStatus == OperationalStatus.Up && - !string.IsNullOrWhiteSpace(nic.GetPhysicalAddress()?.ToString()))?.GetPhysicalAddress()?.ToString(); + try + { + hashMacAddress = null; - hashMacAddress = string.IsNullOrWhiteSpace(value) ? null : GenerateSha256HashString(value)?.Replace("-", string.Empty)?.ToLowerInvariant(); + if (string.IsNullOrEmpty(hashMacAddress)) + { + string value = NetworkInterface.GetAllNetworkInterfaces()? + .FirstOrDefault(nic => nic != null && + nic.OperationalStatus == OperationalStatus.Up && + !string.IsNullOrWhiteSpace(nic.GetPhysicalAddress()?.ToString()))?.GetPhysicalAddress()?.ToString(); + + hashMacAddress = string.IsNullOrWhiteSpace(value) ? null : GenerateSha256HashString(value)?.Replace("-", string.Empty)?.ToLowerInvariant(); + } + } + catch (Exception) + { + // Ignore errors with obtaining the MAC address + } } return hashMacAddress; @@ -103,7 +121,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands cancellationSource = new CancellationTokenSource(); } - httpTracingInterceptor = httpTracingInterceptor ?? new RecordingTracingInterceptor(PartnerSession.Instance.DebugMessages); + httpTracingInterceptor ??= new RecordingTracingInterceptor(PartnerSession.Instance.DebugMessages); ServiceClientTracing.IsEnabled = true; ServiceClientTracing.AddTracingInterceptor(httpTracingInterceptor); @@ -429,6 +447,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// private void LogQosEvent() { + if (qosEvent == null) + { + return; + } + qosEvent.FinishQosEvent(); PageViewTelemetry pageViewTelemetry = new PageViewTelemetry @@ -475,6 +498,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands eventProperties.Add("IsSuccess", qosEvent.IsSuccess.ToString()); eventProperties.Add("ModuleVersion", qosEvent.ModuleVersion); eventProperties.Add("PowerShellVersion", Host.Version.ToString()); + + if (!string.IsNullOrEmpty(PartnerSession.Instance.Context?.Account?.Tenant)) + { + eventProperties.Add("TenantId", PartnerSession.Instance.Context.Account.Tenant); + } } /// diff --git a/src/PowerShell/Commands/RemovePartnerCustomerConfigurationPolicy.cs b/src/PowerShell/Commands/RemovePartnerCustomerConfigurationPolicy.cs index 985b816..33c5d2e 100644 --- a/src/PowerShell/Commands/RemovePartnerCustomerConfigurationPolicy.cs +++ b/src/PowerShell/Commands/RemovePartnerCustomerConfigurationPolicy.cs @@ -41,7 +41,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands return; } - Partner.Customers[CustomerId].ConfigurationPolicies[PolicyId].DeleteAsync().GetAwaiter().GetResult(); + Partner.Customers[CustomerId].ConfigurationPolicies[PolicyId].DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(true); } } diff --git a/src/PowerShell/Commands/RemovePartnerCustomerUser.cs b/src/PowerShell/Commands/RemovePartnerCustomerUser.cs index 5412d55..652c51a 100644 --- a/src/PowerShell/Commands/RemovePartnerCustomerUser.cs +++ b/src/PowerShell/Commands/RemovePartnerCustomerUser.cs @@ -75,7 +75,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); userId.AssertNotEmpty(nameof(userId)); - Partner.Customers.ById(customerId).Users.ById(userId).DeleteAsync().GetAwaiter().GetResult(); + Partner.Customers.ById(customerId).Users.ById(userId).DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(true); } @@ -132,13 +132,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands users = new List(); - seekUsers = Partner.Customers[customerId].Users.GetAsync().GetAwaiter().GetResult(); + seekUsers = Partner.Customers[customerId].Users.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); usersEnumerator = Partner.Enumerators.CustomerUsers.Create(seekUsers); while (usersEnumerator.HasValue) { users.AddRange(usersEnumerator.Current.Items); - usersEnumerator.NextAsync().GetAwaiter().GetResult(); + usersEnumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } return users; diff --git a/src/PowerShell/Commands/RemovePartnerCustomerUserRoleMember.cs b/src/PowerShell/Commands/RemovePartnerCustomerUserRoleMember.cs index c99750b..77d75ac 100644 --- a/src/PowerShell/Commands/RemovePartnerCustomerUserRoleMember.cs +++ b/src/PowerShell/Commands/RemovePartnerCustomerUserRoleMember.cs @@ -42,7 +42,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands UserId.AssertNotEmpty(nameof(UserId)); RoleId.AssertNotEmpty(nameof(RoleId)); - Partner.Customers[CustomerId].DirectoryRoles[RoleId].UserMembers[UserId].DeleteAsync().GetAwaiter().GetResult(); + Partner.Customers[CustomerId].DirectoryRoles[RoleId].UserMembers[UserId].DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(true); } } diff --git a/src/PowerShell/Commands/RemovePartnerResellerRelationship.cs b/src/PowerShell/Commands/RemovePartnerResellerRelationship.cs index a86f874..95834e1 100644 --- a/src/PowerShell/Commands/RemovePartnerResellerRelationship.cs +++ b/src/PowerShell/Commands/RemovePartnerResellerRelationship.cs @@ -35,7 +35,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess(string.Format(CultureInfo.CurrentCulture, Resources.RemovePartnerResellerRelationshipWhatIf, CustomerId))) { - subscriptions = Partner.Customers[CustomerId].Subscriptions.GetAsync().GetAwaiter().GetResult(); + subscriptions = Partner.Customers[CustomerId].Subscriptions.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); foreach (Subscription subscription in subscriptions.Items.Where(s => s.Status == SubscriptionStatus.Active)) { diff --git a/src/PowerShell/Commands/RemovePartnerSandboxCustomer.cs b/src/PowerShell/Commands/RemovePartnerSandboxCustomer.cs index 85e0fe8..24ae217 100644 --- a/src/PowerShell/Commands/RemovePartnerSandboxCustomer.cs +++ b/src/PowerShell/Commands/RemovePartnerSandboxCustomer.cs @@ -29,7 +29,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { if (ShouldProcess(string.Format(CultureInfo.CurrentCulture, Resources.RemovePartnerSandboxCustomerWhatIf, CustomerId))) { - Partner.Customers[CustomerId].DeleteAsync().GetAwaiter().GetResult(); + Partner.Customers[CustomerId].DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(true); } } diff --git a/src/PowerShell/Commands/RestorePartnerCustomerUser.cs b/src/PowerShell/Commands/RestorePartnerCustomerUser.cs index 6dea436..1c7555b 100644 --- a/src/PowerShell/Commands/RestorePartnerCustomerUser.cs +++ b/src/PowerShell/Commands/RestorePartnerCustomerUser.cs @@ -143,7 +143,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands while (usersEnumerator.HasValue) { users.AddRange(usersEnumerator.Current.Items); - usersEnumerator.NextAsync().GetAwaiter().GetResult(); + usersEnumerator.NextAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } return users; diff --git a/src/PowerShell/Commands/SetPartnerAzureBillingProfile.cs b/src/PowerShell/Commands/SetPartnerAzureBillingProfile.cs new file mode 100644 index 0000000..80410ad --- /dev/null +++ b/src/PowerShell/Commands/SetPartnerAzureBillingProfile.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Commands +{ + using System.Management.Automation; + using System.Text.RegularExpressions; + using Azure.Management.Billing; + using Azure.Management.Billing.Models; + using Models.Authentication; + + [Cmdlet(VerbsCommon.Set, "PartnerAzureBillingPolicy")] + [OutputType(typeof(CustomerPolicy))] + public class SetPartnerAzureBillingPolicy : PartnerAsyncCmdlet + { + /// + /// The value that indiciates the customer is able to see charges in the Azure portal. + /// + private const string AllowedValue = "Allowed"; + + /// + /// The value that indiciates the customer is not able to see charges in the Azure portal. + /// + private const string NotAllowedValue = "NotAllowed"; + + /// + /// Gets or sets the name for the billing account. + /// + [Parameter(HelpMessage = "The name for the billing account.", Mandatory = true)] + public string BillingAccountName { get; set; } + + /// + /// Gets or sets the identifier for the customer. + /// + [Parameter(HelpMessage = "The identifier for the customer.", Mandatory = true)] + [ValidatePattern(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", Options = RegexOptions.Compiled | RegexOptions.IgnoreCase)] + public string CustomerId { get; set; } + + /// + /// Gets or sets a flag that indicates whether or not the customer can view charges for Azure services. + /// + [Parameter(HelpMessage = "A flag that indicates whether or not the customer can view charges for Azure services.", Mandatory = true)] + public SwitchParameter ViewCharges { get; set; } + + /// + /// Executes the operations associated with the cmdlet. + /// + public override void ExecuteCmdlet() + { + Scheduler.RunTask(async () => + { + IBillingManagementClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync(new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}/user_impersonation" }); + + WriteObject(await client.Policies.UpdateCustomerAsync( + BillingAccountName, + CustomerId, + new CustomerPolicy + { + ViewCharges = ViewCharges.ToBool() ? AllowedValue : NotAllowedValue + }, + CancellationToken).ConfigureAwait(false)); + }, true); + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Commands/SetPartnerAzureSubscription.cs b/src/PowerShell/Commands/SetPartnerAzureSubscription.cs index ccbbbba..f254425 100644 --- a/src/PowerShell/Commands/SetPartnerAzureSubscription.cs +++ b/src/PowerShell/Commands/SetPartnerAzureSubscription.cs @@ -9,8 +9,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Azure.Management.Profiles.Subscription.Models; using Models.Authentication; - [Cmdlet(VerbsCommon.Set, "PartnerAzureSubscription"), OutputType(typeof(string))] - public class SetPartnerAzureSubscription : PartnerPSCmdlet + [Cmdlet(VerbsCommon.Set, "PartnerAzureSubscription")] + [OutputType(typeof(string))] + public class SetPartnerAzureSubscription : PartnerAsyncCmdlet { /// /// Gets or sets the subscription identifier. @@ -29,7 +30,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// /// Gets or sets the subscription name. /// - [Parameter(HelpMessage = "The display name for the subscription..", Mandatory = true)] + [Parameter(HelpMessage = "The display name for the subscription.", Mandatory = true)] public string SubscriptionName { get; set; } /// @@ -37,19 +38,22 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - ISubscriptionClient client = PartnerSession.Instance.ClientFactory.CreateServiceClient( - new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}//user_impersonation" }, - CustomerId); + Scheduler.RunTask(async () => + { + ISubscriptionClient client = await PartnerSession.Instance.ClientFactory.CreateServiceClientAsync( + new[] { $"{PartnerSession.Instance.Context.Environment.AzureEndpoint}//user_impersonation" }, + CustomerId); - RenamedSubscriptionId value = client.Subscriptions.RenameAsync( - SubscriptionId, - new SubscriptionName - { - SubscriptionNameProperty = SubscriptionName - }, - CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + RenamedSubscriptionId value = client.Subscriptions.RenameAsync( + SubscriptionId, + new SubscriptionName + { + SubscriptionNameProperty = SubscriptionName + }, + CancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); - WriteObject(value.Value); + WriteObject(value.Value); + }, true); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/SetPartnerBillingProfile.cs b/src/PowerShell/Commands/SetPartnerBillingProfile.cs index a5b6aa7..6fbaff8 100644 --- a/src/PowerShell/Commands/SetPartnerBillingProfile.cs +++ b/src/PowerShell/Commands/SetPartnerBillingProfile.cs @@ -117,7 +117,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess("Updates the partner's billing profile")) { - profile = Partner.Profiles.BillingProfile.GetAsync().GetAwaiter().GetResult(); + profile = Partner.Profiles.BillingProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); profile.Address.AddressLine1 = UpdateValue(AddressLine1, profile.Address.AddressLine1); profile.Address.AddressLine2 = UpdateValue(AddressLine2, profile.Address.AddressLine2); diff --git a/src/PowerShell/Commands/SetPartnerCustomer.cs b/src/PowerShell/Commands/SetPartnerCustomer.cs index 3712325..7921292 100644 --- a/src/PowerShell/Commands/SetPartnerCustomer.cs +++ b/src/PowerShell/Commands/SetPartnerCustomer.cs @@ -140,7 +140,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands throw new PSInvalidOperationException(Resources.InvalidSetCustomerIdentifierException); } - customer = Partner.Customers[customerId].GetAsync().GetAwaiter().GetResult(); + customer = Partner.Customers[customerId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); customer.BillingProfile.DefaultAddress.AddressLine1 = UpdateValue(BillingAddressLine1, customer.BillingProfile.DefaultAddress.AddressLine1); customer.BillingProfile.DefaultAddress.AddressLine2 = UpdateValue(BillingAddressLine2, customer.BillingProfile.DefaultAddress.AddressLine2); diff --git a/src/PowerShell/Commands/SetPartnerCustomerCart.cs b/src/PowerShell/Commands/SetPartnerCustomerCart.cs index bd33d23..663c7e4 100644 --- a/src/PowerShell/Commands/SetPartnerCustomerCart.cs +++ b/src/PowerShell/Commands/SetPartnerCustomerCart.cs @@ -53,7 +53,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands return; } - cart = Partner.Customers[CustomerId].Carts[CartId].GetAsync().GetAwaiter().GetResult(); + cart = Partner.Customers[CustomerId].Carts[CartId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); cartLineItems = new List(); diff --git a/src/PowerShell/Commands/SetPartnerCustomerConfigurationPolicy.cs b/src/PowerShell/Commands/SetPartnerCustomerConfigurationPolicy.cs index c7dfc07..84e369c 100644 --- a/src/PowerShell/Commands/SetPartnerCustomerConfigurationPolicy.cs +++ b/src/PowerShell/Commands/SetPartnerCustomerConfigurationPolicy.cs @@ -142,7 +142,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands customerId.AssertNotEmpty(nameof(customerId)); policyId.AssertNotEmpty(nameof(policyId)); - return Partner.Customers[customerId].ConfigurationPolicies[policyId].GetAsync().GetAwaiter().GetResult(); + return Partner.Customers[customerId].ConfigurationPolicies[policyId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/SetPartnerCustomerSubscription.cs b/src/PowerShell/Commands/SetPartnerCustomerSubscription.cs index d682ed7..525bfd9 100644 --- a/src/PowerShell/Commands/SetPartnerCustomerSubscription.cs +++ b/src/PowerShell/Commands/SetPartnerCustomerSubscription.cs @@ -85,7 +85,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands return; } - subscription = Partner.Customers[customerId].Subscriptions[SubscriptionId].GetAsync().GetAwaiter().GetResult(); + subscription = Partner.Customers[customerId].Subscriptions[SubscriptionId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (BillingCycle.HasValue) diff --git a/src/PowerShell/Commands/SetPartnerCustomerUser.cs b/src/PowerShell/Commands/SetPartnerCustomerUser.cs index 73eba85..a7e5837 100644 --- a/src/PowerShell/Commands/SetPartnerCustomerUser.cs +++ b/src/PowerShell/Commands/SetPartnerCustomerUser.cs @@ -95,7 +95,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands UserId = (InputObject == null) ? UserId : InputObject.UserId; - user = Partner.Customers[CustomerId].Users[UserId].GetAsync().GetAwaiter().GetResult(); + user = Partner.Customers[CustomerId].Users[UserId].GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (user.Id == UserId) { diff --git a/src/PowerShell/Commands/SetPartnerLegalProfile.cs b/src/PowerShell/Commands/SetPartnerLegalProfile.cs index 7902ddb..8b6a8ce 100644 --- a/src/PowerShell/Commands/SetPartnerLegalProfile.cs +++ b/src/PowerShell/Commands/SetPartnerLegalProfile.cs @@ -111,7 +111,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess(Resources.SetPartnerLegalProfileWhatIf)) { - profile = Partner.Profiles.LegalBusinessProfile.GetAsync().GetAwaiter().GetResult(); + profile = Partner.Profiles.LegalBusinessProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); profile.Address.AddressLine1 = UpdateValue(AddressLine1, profile.Address.AddressLine1); profile.Address.AddressLine2 = UpdateValue(AddressLine2, profile.Address.AddressLine2); diff --git a/src/PowerShell/Commands/SetPartnerOrganizationProfile.cs b/src/PowerShell/Commands/SetPartnerOrganizationProfile.cs index 1ee6314..d9af562 100644 --- a/src/PowerShell/Commands/SetPartnerOrganizationProfile.cs +++ b/src/PowerShell/Commands/SetPartnerOrganizationProfile.cs @@ -124,7 +124,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess("Updates the partner's organization profile")) { - profile = Partner.Profiles.OrganizationProfile.GetAsync().GetAwaiter().GetResult(); + profile = Partner.Profiles.OrganizationProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); profile.CompanyName = UpdateValue(CompanyName, profile.CompanyName); profile.Email = UpdateValue(Email, profile.Email); diff --git a/src/PowerShell/Commands/SetPartnerSupportProfile.cs b/src/PowerShell/Commands/SetPartnerSupportProfile.cs index 5100eba..0a213d3 100644 --- a/src/PowerShell/Commands/SetPartnerSupportProfile.cs +++ b/src/PowerShell/Commands/SetPartnerSupportProfile.cs @@ -43,7 +43,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands { SupportProfile profile; - profile = Partner.Profiles.SupportProfile.GetAsync().GetAwaiter().GetResult(); + profile = Partner.Profiles.SupportProfile.GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); profile.Email = UpdateValue(SupportEmail, profile.Email); profile.Telephone = UpdateValue(SupportPhoneNumber, profile.Telephone); diff --git a/src/PowerShell/Commands/SubmitPartnerCustomerCart.cs b/src/PowerShell/Commands/SubmitPartnerCustomerCart.cs index c80cae3..736d1bc 100644 --- a/src/PowerShell/Commands/SubmitPartnerCustomerCart.cs +++ b/src/PowerShell/Commands/SubmitPartnerCustomerCart.cs @@ -39,7 +39,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands if (ShouldProcess(string.Format(CultureInfo.CurrentCulture, Resources.CheckoutPartnerCustomerCartWhatIf, CartId))) { - checkoutResult = Partner.Customers[CustomerId].Carts[CartId].CheckoutAsync().GetAwaiter().GetResult(); + checkoutResult = Partner.Customers[CustomerId].Carts[CartId].CheckoutAsync().ConfigureAwait(false).GetAwaiter().GetResult(); WriteObject(new PSCartCheckoutResult(checkoutResult)); } } diff --git a/src/PowerShell/Commands/TestPartnerAddress.cs b/src/PowerShell/Commands/TestPartnerAddress.cs index 3e11621..ee10172 100644 --- a/src/PowerShell/Commands/TestPartnerAddress.cs +++ b/src/PowerShell/Commands/TestPartnerAddress.cs @@ -8,57 +8,58 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Validations; /// - /// Verifies the specified is valid. + /// Verifies the specified address is valid. /// - [Cmdlet(VerbsDiagnostic.Test, "PartnerAddress", SupportsShouldProcess = true), OutputType(typeof(bool))] + [Cmdlet(VerbsDiagnostic.Test, "PartnerAddress")] + [OutputType(typeof(bool))] public class TestPartnerAddress : PartnerCmdlet { /// /// Gets or sets the first line of the address. /// - [Parameter(Mandatory = true, HelpMessage = "The first line of the address.")] + [Parameter(HelpMessage = "The first line of the address.", Mandatory = true)] [ValidateNotNullOrEmpty] public string AddressLine1 { get; set; } /// /// Gets or sets the second line of the address. /// - [Parameter(Mandatory = false, HelpMessage = "The second line of the adress.")] + [Parameter(HelpMessage = "The second line of the adress.", Mandatory = false)] [ValidateNotNullOrEmpty] public string AddressLine2 { get; set; } /// /// Gets or sets the city portion of the address. /// - [Parameter(Mandatory = false, HelpMessage = "The city portion of the address.")] + [Parameter(HelpMessage = "The city portion of the address.", Mandatory = false)] [ValidateNotNullOrEmpty] public string City { get; set; } /// /// Gets or sets the country portion of the address. /// - [Parameter(Mandatory = false, HelpMessage = "The country portion of the address.")] + [Parameter(HelpMessage = "The country portion of the address.", Mandatory = false)] [ValidateNotNullOrEmpty] public string Country { get; set; } /// /// Gets or sets the postal code portion of the address. /// - [Parameter(Mandatory = true, HelpMessage = "The postal code portion of the address.")] + [Parameter(HelpMessage = "The postal code portion of the address.", Mandatory = false)] [ValidateNotNullOrEmpty] public string PostalCode { get; set; } /// /// Gets or sets the region portion of the address. /// - [Parameter(Mandatory = false, HelpMessage = "The region portion of the address.")] + [Parameter(HelpMessage = "The region portion of the address.", Mandatory = false)] [ValidateNotNullOrEmpty] public string Region { get; set; } /// /// Gets or sets the state portion of the address. /// - [Parameter(Mandatory = false, HelpMessage = "The state portion of the address.")] + [Parameter(HelpMessage = "The state portion of the address.", Mandatory = false)] [ValidateNotNullOrEmpty] public string State { get; set; } diff --git a/src/PowerShell/Commands/TestPartnerDomainAvailability.cs b/src/PowerShell/Commands/TestPartnerDomainAvailability.cs index 318f636..64422af 100644 --- a/src/PowerShell/Commands/TestPartnerDomainAvailability.cs +++ b/src/PowerShell/Commands/TestPartnerDomainAvailability.cs @@ -24,7 +24,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands /// public override void ExecuteCmdlet() { - WriteObject(!Partner.Domains.ByDomain(Domain).ExistsAsync().GetAwaiter().GetResult()); + WriteObject(!Partner.Domains.ByDomain(Domain).ExistsAsync().ConfigureAwait(false).GetAwaiter().GetResult()); } } } \ No newline at end of file diff --git a/src/PowerShell/Commands/TestPartnerSecurityRequirement.cs b/src/PowerShell/Commands/TestPartnerSecurityRequirement.cs index 0d2851b..84807a2 100644 --- a/src/PowerShell/Commands/TestPartnerSecurityRequirement.cs +++ b/src/PowerShell/Commands/TestPartnerSecurityRequirement.cs @@ -11,7 +11,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands using Models.Authentication; [Cmdlet(VerbsDiagnostic.Test, "PartnerSecurityRequirement")] - public class TestPartnerSecurityRequirement : PartnerPSCmdlet + public class TestPartnerSecurityRequirement : PartnerAsyncCmdlet { /// /// The message written to the console. @@ -47,7 +47,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands PartnerAccount account = new PartnerAccount { - Tenant = "common", + Tenant = "organizations", Type = AccountType.User }; @@ -64,40 +64,41 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands account.SetProperty(PartnerAccountPropertyType.ApplicationId, PowerShellApplicationId); - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( - account, - environment, - new[] { $"{environment.PartnerCenterEndpoint}/user_impersonation" }, - Message, - WriteWarning, - WriteDebug, - CancellationToken); - - - JsonWebToken jwt = new JsonWebToken(authResult.AccessToken); - - WriteDebug("Checking if the access token contains the MFA claim..."); - - /* - * Obtain the authentication method reference (AMR) claim. This claim contains the methods used - * during authenitcation. See https://tools.ietf.org/html/rfc8176 for more information. - */ - - if (jwt.TryGetClaim("amr", out Claim claim)) + Scheduler.RunTask(async () => { - if (!claim.Value.Contains("mfa")) + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( + account, + environment, + new[] { $"{environment.PartnerCenterEndpoint}/user_impersonation" }, + Message, + CancellationToken).ConfigureAwait(false); + + + JsonWebToken jwt = new JsonWebToken(authResult.AccessToken); + + WriteDebug("Checking if the access token contains the MFA claim..."); + + /* + * Obtain the authentication method reference (AMR) claim. This claim contains the methods used + * during authenitcation. See https://tools.ietf.org/html/rfc8176 for more information. + */ + + if (jwt.TryGetClaim("amr", out Claim claim)) { - WriteWarning("Unable to determine if the account authenticated using MFA. See https://aka.ms/partnercenterps-psr-warning for more information."); + if (!claim.Value.Contains("mfa")) + { + WriteWarning("Unable to determine if the account authenticated using MFA. See https://aka.ms/partnercenterps-psr-warning for more information."); + result = "fail"; + } + } + else + { + WriteWarning("Unable to find the AMR claim, which means the ability to verify the MFA challenge happened will not be possible. See https://aka.ms/partnercenterps-psr-warning for more information."); result = "fail"; } - } - else - { - WriteWarning("Unable to find the AMR claim, which means the ability to verify the MFA challenge happened will not be possible. See https://aka.ms/partnercenterps-psr-warning for more information."); - result = "fail"; - } - WriteObject(result); + WriteObject(result); + }); } } } diff --git a/src/PowerShell/Factories/AuthenticationFactory.cs b/src/PowerShell/Factories/AuthenticationFactory.cs index f229851..caf484b 100644 --- a/src/PowerShell/Factories/AuthenticationFactory.cs +++ b/src/PowerShell/Factories/AuthenticationFactory.cs @@ -19,14 +19,14 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories { internal IAuthenticatorBuilder Builder => new DefaultAuthenticatorBuilder(); - public AuthenticationResult Authenticate(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null, Action promptAction = null, Action debugAction = null, CancellationToken cancellationToken = default) + public async Task AuthenticateAsync(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null, CancellationToken cancellationToken = default) { AuthenticationResult authResult = null; IAuthenticator processAuthenticator = Builder.Authenticator; - while (processAuthenticator != null && processAuthenticator.TryAuthenticate(GetAuthenticationParameters(account, environment, scopes, message), out Task result, promptAction, cancellationToken)) + while (processAuthenticator != null && authResult == null) { - authResult = result.ConfigureAwait(true).GetAwaiter().GetResult(); + authResult = await processAuthenticator.TryAuthenticateAsync(GetAuthenticationParameters(account, environment, scopes, message), cancellationToken); if (authResult != null) { @@ -36,7 +36,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories account.ObjectId = authResult.Account.HomeAccountId.ObjectId; } - if (account.Tenant.Equals("common", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(authResult.TenantId)) + if (account.Tenant.Equals("organizations", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(authResult.TenantId)) { account.Tenant = authResult.TenantId; } @@ -47,7 +47,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories processAuthenticator = processAuthenticator.Next; } - return authResult ?? null; + return authResult; } private AuthenticationParameters GetAuthenticationParameters(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null) diff --git a/src/PowerShell/Factories/ClientFactory.cs b/src/PowerShell/Factories/ClientFactory.cs index 2aeadeb..715ba6a 100644 --- a/src/PowerShell/Factories/ClientFactory.cs +++ b/src/PowerShell/Factories/ClientFactory.cs @@ -7,6 +7,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories using System.Collections.Generic; using System.Net.Http; using System.Reflection; + using System.Threading.Tasks; using Extensions; using Graph; using Identity.Client; @@ -19,14 +20,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// public class ClientFactory : IClientFactory { - private static readonly CancelRetryHandler DefaultCancelRetryHandler = new CancelRetryHandler(3, TimeSpan.FromSeconds(10)); - /// /// The service client used to communicate with Microsoft Graph. /// - private static readonly IGraphServiceClient GraphServiceClient = new GraphServiceClient(null, new HttpProvider(new CancelRetryHandler(3, TimeSpan.FromSeconds(10)) + private static readonly IGraphServiceClient GraphServiceClient = new GraphServiceClient(null, new HttpProvider(new RetryDelegatingHandler { - InnerHandler = new RetryDelegatingHandler + InnerHandler = new ClientTracingHandler { InnerHandler = new HttpClientHandler() } @@ -35,12 +34,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// /// The client used to perform HTTP operations. /// - private static readonly HttpClient HttpClient = new HttpClient(new CancelRetryHandler(3, TimeSpan.FromSeconds(10)) + private static readonly HttpClient HttpClient = new HttpClient(new RetryDelegatingHandler { - InnerHandler = new RetryDelegatingHandler - { - InnerHandler = new HttpClientHandler() - } + InnerHandler = new HttpClientHandler() }); /// @@ -57,13 +53,22 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// /// An instance of the class. public virtual IPartner CreatePartnerOperations() + { + return CreatePartnerOperationsAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Creates a new instance of the object used to interface with Partner Center. + /// + /// An instance of the class. + public virtual async Task CreatePartnerOperationsAsync() { PartnerService.Instance.ApiRootUrl = new Uri(PartnerSession.Instance.Context.Environment.PartnerCenterEndpoint); - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( PartnerSession.Instance.Context.Account, PartnerSession.Instance.Context.Environment, - new[] { PartnerSession.Instance.Context.Account.GetProperty(PartnerAccountPropertyType.Scope) }); + new[] { PartnerSession.Instance.Context.Account.GetProperty(PartnerAccountPropertyType.Scope) }).ConfigureAwait(false); return PartnerService.Instance.CreatePartnerOperations( new PowerShellCredentials(new AuthenticationToken(authResult.AccessToken, authResult.ExpiresOn)), @@ -77,7 +82,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// Scopes requested to access a protected service. /// The identifier for the tenant. /// An instance of a service client that is connected to a specific service. - public virtual TClient CreateServiceClient(string[] scopes, string tenantId = null) where TClient : ServiceClient + public virtual async Task CreateServiceClientAsync(string[] scopes, string tenantId = null) where TClient : ServiceClient { PartnerAccount account = PartnerSession.Instance.Context.Account; @@ -86,12 +91,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories account.Tenant = tenantId; } - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( account, PartnerSession.Instance.Context.Environment, - scopes); + scopes).ConfigureAwait(false); - return CreateServiceClient(new TokenCredentials(authResult.AccessToken, "Bearer")); + return CreateServiceClient(new TokenCredentials(authResult.AccessToken, "Bearer"), HttpClient, false); } private TClient CreateServiceClient(params object[] parameters) where TClient : ServiceClient @@ -99,18 +104,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories List types = new List(); List parameterList = new List(); - List handlerList = new List { DefaultCancelRetryHandler.Clone() as CancelRetryHandler }; - foreach (object obj in parameters) { - Type paramType = obj.GetType(); - types.Add(paramType); + types.Add(obj.GetType()); parameterList.Add(obj); } - types.Add((Array.Empty()).GetType()); - parameterList.Add(handlerList.ToArray()); - ConstructorInfo constructor = typeof(TClient).GetConstructor(types.ToArray()); if (constructor == null) diff --git a/src/PowerShell/Factories/IAuthenticationFactory.cs b/src/PowerShell/Factories/IAuthenticationFactory.cs index 623b8dc..9d1e353 100644 --- a/src/PowerShell/Factories/IAuthenticationFactory.cs +++ b/src/PowerShell/Factories/IAuthenticationFactory.cs @@ -3,9 +3,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories { - using System; using System.Collections.Generic; using System.Threading; + using System.Threading.Tasks; using Identity.Client; using Models.Authentication; @@ -18,6 +18,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// Acquires the security token from the authority. /// /// The result from the authentication request. - AuthenticationResult Authenticate(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null, Action promptAction = null, Action debugAction = null, CancellationToken cancellationToken = default); + Task AuthenticateAsync(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/PowerShell/Factories/IClientFactory.cs b/src/PowerShell/Factories/IClientFactory.cs index c22d7dd..b6f1dbb 100644 --- a/src/PowerShell/Factories/IClientFactory.cs +++ b/src/PowerShell/Factories/IClientFactory.cs @@ -3,6 +3,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories { + using System.Threading.Tasks; using Graph; using Rest; @@ -23,6 +24,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// An instance of the class. IPartner CreatePartnerOperations(); + /// + /// Creates a new instance of the object used to interface with Partner Center. + /// + /// An instance of the class. + Task CreatePartnerOperationsAsync(); + /// /// Creates a new service client used interact with a specific service. /// @@ -30,6 +37,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories /// Scopes requested to access a protected service. /// The identifier for the tenant. /// An instance of a service client that is connected to a specific service. - TClient CreateServiceClient(string[] scopes, string tenantId = null) where TClient : ServiceClient; + Task CreateServiceClientAsync(string[] scopes, string tenantId = null) where TClient : ServiceClient; } } \ No newline at end of file diff --git a/src/PowerShell/Factories/SharedTokenCacheClientFactory.cs b/src/PowerShell/Factories/SharedTokenCacheClientFactory.cs index 90cb331..8654880 100644 --- a/src/PowerShell/Factories/SharedTokenCacheClientFactory.cs +++ b/src/PowerShell/Factories/SharedTokenCacheClientFactory.cs @@ -18,8 +18,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories private static readonly string CacheFilePath = Path.Combine(SharedUtilities.GetUserRootDirectory(), ".IdentityService", CacheFileName); - private static ITokenCache tokenCache; - private static MsalCacheHelper InitializeCacheHelper(string clientId) { StorageCreationPropertiesBuilder builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), clientId); @@ -37,6 +35,26 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories return MsalCacheHelper.CreateAsync(storageCreationProperties).ConfigureAwait(false).GetAwaiter().GetResult(); } + public static MsalCacheStorage GetMsalCacheStorage(string clientId) + { + StorageCreationPropertiesBuilder builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), clientId); + + builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache"); + builder = builder.WithLinuxKeyring( + schemaName: "msal.cache", + collection: "default", + secretLabel: "MSALCache", + attribute1: new KeyValuePair("MsalClientID", "Microsoft.Developer.IdentityService"), + attribute2: new KeyValuePair("MsalClientVersion", "1.0.0.0")); + + return new MsalCacheStorage(builder.Build()); + } + + public static string GetTokenCacheKey(AuthenticationResult authResult, string applicationId) + { + return $"{authResult.Account.HomeAccountId.Identifier}-{authResult.Account.Environment}-RefreshToken-{applicationId}--"; + } + public static IConfidentialClientApplication CreateConfidentialClient( string authority = null, string clientId = null, @@ -113,33 +131,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories enablePiiLogging: false, enableDefaultPlatformLogging: true).Build(); + MsalCacheHelper cacheHelper = InitializeCacheHelper(clientId); cacheHelper.RegisterCache(client.UserTokenCache); return client; } - public static ITokenCache GetTokenCache(string clientId) - { - if (tokenCache == null) - { - PublicClientApplicationBuilder builder = PublicClientApplicationBuilder.Create(clientId); - - IPublicClientApplication client = builder.WithLogging( - DebugLoggingMethod, - LogLevel.Info, - enablePiiLogging: false, - enableDefaultPlatformLogging: true).Build(); - - MsalCacheHelper cacheHelper = InitializeCacheHelper(clientId); - cacheHelper.RegisterCache(client.UserTokenCache); - - tokenCache = client.UserTokenCache; - } - - return tokenCache; - } - private static void DebugLoggingMethod(LogLevel level, string message, bool containsPii) { PartnerSession.Instance.DebugMessages.Enqueue($"MSAL {level} {containsPii} {message}"); diff --git a/src/PowerShell/Models/Authentication/AuthResult.cs b/src/PowerShell/Models/Authentication/AuthResult.cs index 52cd5c4..32d8d01 100644 --- a/src/PowerShell/Models/Authentication/AuthResult.cs +++ b/src/PowerShell/Models/Authentication/AuthResult.cs @@ -9,8 +9,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication public class AuthResult : AuthenticationResult { - public AuthResult(string accessToken, bool isExtendedLifeTimeToken, string uniqueId, DateTimeOffset expiresOn, DateTimeOffset extendedExpiresOn, string tenantId, IAccount account, string idToken, IEnumerable scopes) - : base(accessToken, isExtendedLifeTimeToken, uniqueId, expiresOn, extendedExpiresOn, tenantId, account, idToken, scopes) + public AuthResult(string accessToken, bool isExtendedLifeTimeToken, string uniqueId, DateTimeOffset expiresOn, DateTimeOffset extendedExpiresOn, string tenantId, IAccount account, string idToken, IEnumerable scopes, Guid correlationId) + : base(accessToken, isExtendedLifeTimeToken, uniqueId, expiresOn, extendedExpiresOn, tenantId, account, idToken, scopes, correlationId) { } diff --git a/src/PowerShell/Models/Authentication/ModuleName.cs b/src/PowerShell/Models/Authentication/ModuleName.cs new file mode 100644 index 0000000..c52fc08 --- /dev/null +++ b/src/PowerShell/Models/Authentication/ModuleName.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication +{ + /// + /// Collection of names for the modules where connection information is known. + /// + public enum ModuleName + { + /// + /// Name of the Exchange Online PowerShell module. + /// + ExchangeOnline + } +} \ No newline at end of file diff --git a/src/PowerShell/Models/Authentication/PartnerAccountPropertyType.cs b/src/PowerShell/Models/Authentication/PartnerAccountPropertyType.cs index 973d83c..6d0b55c 100644 --- a/src/PowerShell/Models/Authentication/PartnerAccountPropertyType.cs +++ b/src/PowerShell/Models/Authentication/PartnerAccountPropertyType.cs @@ -14,7 +14,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication public const string AccessToken = "AccessToken"; /// - /// Name of the application identifier extended property0. + /// Name of the application identifier extended property. /// public const string ApplicationId = "ApplicationId"; @@ -37,5 +37,15 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication /// Name of the service principal secret extended property. /// public const string ServicePrincipalSecret = "ServicePrincipalSecret"; + + /// + /// Name of the use authorization code extended property. + /// + public const string UseAuthCode = "UseAuthCode"; + + /// + /// Name of the use device authentication extended property. + /// + public const string UseDeviceAuth = "UseDeviceAuth"; } } \ No newline at end of file diff --git a/src/PowerShell/Models/Authentication/PartnerSession.cs b/src/PowerShell/Models/Authentication/PartnerSession.cs index 57bf8a8..fa8074f 100644 --- a/src/PowerShell/Models/Authentication/PartnerSession.cs +++ b/src/PowerShell/Models/Authentication/PartnerSession.cs @@ -5,6 +5,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication { using System; using System.Collections.Concurrent; + using System.Collections.Generic; using System.Threading; using Factories; @@ -13,6 +14,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication /// public class PartnerSession { + /// + /// Provides a registry for various components. + /// + private readonly IDictionary componentRegistry = new ConcurrentDictionary(); + /// /// Singleton instance of the class. /// @@ -62,5 +68,38 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication } } } + + public void RegisterComponent(string componentName, Func componentInitializer) where T : class + { + RegisterComponent(componentName, componentInitializer, false); ; + } + + public void RegisterComponent(string componentName, Func componentInitializer, bool overwrite) where T : class + { + if (!componentRegistry.ContainsKey(componentName) || overwrite) + { + componentRegistry[componentName] = componentInitializer(); + } + } + + public bool TryGetComponent(string componentName, out T component) where T : class + { + component = null; + + if (componentRegistry.ContainsKey(componentName)) + { + component = componentRegistry[componentName] as T; + } + + return component != null; + } + + public void UnregisterComponent(string componentName) where T : class + { + if (componentRegistry.ContainsKey(componentName)) + { + componentRegistry.Remove(componentName); + } + } } } \ No newline at end of file diff --git a/src/PowerShell/Models/Authentication/PowerShellCredentials.cs b/src/PowerShell/Models/Authentication/PowerShellCredentials.cs index 614af47..cae8f9f 100644 --- a/src/PowerShell/Models/Authentication/PowerShellCredentials.cs +++ b/src/PowerShell/Models/Authentication/PowerShellCredentials.cs @@ -64,14 +64,12 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication { context.AssertNotNull(nameof(context)); - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( PartnerSession.Instance.Context.Account, PartnerSession.Instance.Context.Environment, - new[] { $"{PartnerSession.Instance.Context.Environment.PartnerCenterEndpoint}/user_impersonation" }); + new[] { $"{PartnerSession.Instance.Context.Environment.PartnerCenterEndpoint}/user_impersonation" }).ConfigureAwait(false); authToken = new AuthenticationToken(authResult.AccessToken, authResult.ExpiresOn); - - await Task.CompletedTask.ConfigureAwait(false); } /// diff --git a/src/PowerShell/Models/Authentication/PowerShellModule.cs b/src/PowerShell/Models/Authentication/PowerShellModule.cs new file mode 100644 index 0000000..ff8946d --- /dev/null +++ b/src/PowerShell/Models/Authentication/PowerShellModule.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication +{ + using System.Collections.Concurrent; + using System.Collections.Generic; + + /// + /// Metadata information for PowerShell modules where the connection information is known. + /// + public class PowerShellModule + { + /// + /// The application identifier for the Exchange Online PowerShell module. + /// + private const string ExchangeOnlineApplicationId = "a0c73c16-a7e3-4564-9a95-2bdf47383716"; + + /// + /// The application identifier used for authentication. + /// + public string ApplicationId { get; set; } + + /// + /// Gets the name of the module. + /// + public ModuleName ModuleName { get; private set; } + + /// + /// Gets the scopes used by the module for authentication. + /// + public IEnumerable Scopes { get; private set; } + + /// + /// Gets the modules where connection information is known. + /// + public static IDictionary KnownModules { get; } = InitializeModules(); + + /// + /// Initializes a list of known modules. + /// + /// A dictionary containing the known modules. + private static IDictionary InitializeModules() + { + return new ConcurrentDictionary + { + [ModuleName.ExchangeOnline] = new PowerShellModule + { + ApplicationId = ExchangeOnlineApplicationId, + ModuleName = ModuleName.ExchangeOnline, + Scopes = new[] { "https://outlook.office365.com/.default" } + } + }; + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Models/Authentication/ResourceAccount.cs b/src/PowerShell/Models/Authentication/ResourceAccount.cs new file mode 100644 index 0000000..330e055 --- /dev/null +++ b/src/PowerShell/Models/Authentication/ResourceAccount.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication +{ + using Identity.Client; + + /// + /// Represents information about a single account. + /// + public sealed class ResourceAccount : IAccount + { + /// + /// Initializes a new instance of the class. + /// + /// A string containing the identity provider for this account. + /// The unique identifier for the account. + /// A string representation for a GUID which is the ID of the user owning the account in the tenant. + /// A string representation for a GUID, which is the ID of the tenant where the account resides. + /// A string containing the displayable value in UserPrincipalName (UPN) format. + public ResourceAccount(string environment, string identifier, string objectId, string tenantId, string username) + { + Environment = environment; + HomeAccountId = new AccountId(identifier, objectId, tenantId); + Username = username; + } + + /// + /// Gets a string containing the identity provider for this account, e.g. login.microsoftonline.com. + /// + public string Environment { get; } + + /// + /// AccountId of the home account for the user. This uniquely identifies the user across AAD tenants. + /// + public AccountId HomeAccountId { get; } + + /// + /// Gets a string containing the displayable value in UserPrincipalName (UPN) format, e.g. john.doe@contoso.com. This can be null. + /// + public string Username { get; } + } +} \ No newline at end of file diff --git a/src/PowerShell/Models/Errors/PartnerExceptionRecord.cs b/src/PowerShell/Models/Errors/PartnerExceptionRecord.cs index c8f844c..aa9d0fc 100644 --- a/src/PowerShell/Models/Errors/PartnerExceptionRecord.cs +++ b/src/PowerShell/Models/Errors/PartnerExceptionRecord.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. - namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Errors { using System; diff --git a/src/PowerShell/Models/StreamEventArgs.cs b/src/PowerShell/Models/StreamEventArgs.cs new file mode 100644 index 0000000..ca07ee4 --- /dev/null +++ b/src/PowerShell/Models/StreamEventArgs.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Models +{ + using System; + + /// + /// Represents the stream event arguments. + /// + public class StreamEventArgs : EventArgs + { + /// + /// Gets or sets a flag indiciating whether or not the collection should be enumerated. + /// + public bool EnumerateCollection { get; set; } + + /// + /// Gets or sets the resource to be sent to the pipeline. + /// + public object Resource { get; set; } + } +} \ No newline at end of file diff --git a/src/PowerShell/Models/Subscriptions/PSAzureEntitlement.cs b/src/PowerShell/Models/Subscriptions/PSAzureEntitlement.cs new file mode 100644 index 0000000..335ebfe --- /dev/null +++ b/src/PowerShell/Models/Subscriptions/PSAzureEntitlement.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Subscriptions +{ + using Extensions; + using PartnerCenter.Models.Subscriptions; + + /// + /// Represents an Azure entitlement. + /// + public sealed class PSAzureEntitlement + { + /// + /// Initializes a new instance of the class. + /// + public PSAzureEntitlement() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The base entitlement for this instance. + public PSAzureEntitlement(AzureEntitlement entitlement) + { + this.CopyFrom(entitlement); + } + + /// + /// Gets or sets the friendly name of the entitlement. + /// + public string FriendlyName { get; set; } + + /// + /// Gets or sets the identifier of the entitlement. + /// + public string Id { get; set; } + + /// + /// Gets or sets the status of the entitlement. + /// + public string Status { get; set; } + + /// + /// Gets or sets the subscription identifier of the entitlement. + /// + public string SubscriptionId { get; set; } + } +} \ No newline at end of file diff --git a/src/PowerShell/Network/CancelRetryHandler.cs b/src/PowerShell/Network/CancelRetryHandler.cs deleted file mode 100644 index 871539d..0000000 --- a/src/PowerShell/Network/CancelRetryHandler.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.Store.PartnerCenter.PowerShell.Network -{ - using System; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Delegating handler that retries any operation that was aborted due to the being thrown. - /// - public class CancelRetryHandler : DelegatingHandler, ICloneable - { - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of times the operation should be retried. - /// The interval to wait between retries. - public CancelRetryHandler(int maxTries, TimeSpan waitInterval) - { - MaxTries = maxTries; - WaitInterval = waitInterval; - } - - /// - /// Gets or sets the maximum number of times the operation should be retried. - /// - public int MaxTries { get; set; } = 3; - - /// - /// Gets or sets the interval to wait between retries. - /// - public TimeSpan WaitInterval { get; set; } = TimeSpan.Zero; - - public object Clone() - { - return new CancelRetryHandler(MaxTries, WaitInterval); - } - - /// - /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation. - /// - /// The HTTP request message to send to the server. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// The response from the execution of the operation. - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - int tries = 0; - - do - { - using (CancellationTokenSource source = new CancellationTokenSource()) - { - try - { - return await base.SendAsync(request, source.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) when (tries++ < MaxTries) - { - Thread.Sleep(WaitInterval); - } - } - } - while (true); - } - } -} \ No newline at end of file diff --git a/src/PowerShell/Network/ClientTracingHandler.cs b/src/PowerShell/Network/ClientTracingHandler.cs new file mode 100644 index 0000000..e50a4b4 --- /dev/null +++ b/src/PowerShell/Network/ClientTracingHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Network +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Rest; + + /// + /// Delegating handler that provides tracing the request and response. + /// + public sealed class ClientTracingHandler : DelegatingHandler + { + /// + /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation. + /// + /// The HTTP request message to send to the server. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// The response from the execution of the operation. + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response; + string invocationId = null; + + if (ServiceClientTracing.IsEnabled) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + + NameValueCollection queryParameters = HttpUtility.ParseQueryString(request.RequestUri.Query); + Dictionary tracingParameters = new Dictionary(); + + foreach (string key in queryParameters.AllKeys) + { + tracingParameters.Add(key, queryParameters[key]); + } + + ServiceClientTracing.Enter(invocationId, this, "Send", tracingParameters); + ServiceClientTracing.SendRequest(invocationId, request); + } + + cancellationToken.ThrowIfCancellationRequested(); + response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + + if (ServiceClientTracing.IsEnabled) + { + ServiceClientTracing.ReceiveResponse(invocationId, response); + //ServiceClientTracing.Exit(invocationId, null); + } + + return response; + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Network/DefaultOsBrowserWebUi.cs b/src/PowerShell/Network/DefaultOsBrowserWebUi.cs index 4aef050..83e75de 100644 --- a/src/PowerShell/Network/DefaultOsBrowserWebUi.cs +++ b/src/PowerShell/Network/DefaultOsBrowserWebUi.cs @@ -4,7 +4,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network { using System; - using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; @@ -14,6 +13,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network using System.Web; using Extensions; using Identity.Client.Extensibility; + using Microsoft.Store.PartnerCenter.PowerShell.Models; + using Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication; /// /// Provide a custom Web UI for public client applications to sign-in users and have them consent part of the Authorization code flow. @@ -47,19 +48,15 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network /// private readonly string message; - private readonly Queue messages; - /// /// Initializes a new instance of the class. /// /// The message written to the console. - public DefaultOsBrowserWebUi(Queue messages, string message) + public DefaultOsBrowserWebUi(string message) { message.AssertNotEmpty(nameof(message)); - messages.AssertNotNull(nameof(messages)); this.message = message; - this.messages = messages; } /// @@ -71,14 +68,14 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network /// The URI returned back from the STS authorization endpoint. This URI contains a code=CODE parameter that MSAL.NET will extract and redeem. public async Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) { - messages.Enqueue("Attempting to launch a browser for authorization code login."); + WriteWarning("Attempting to launch a browser for authorization code login."); if (!OpenBrowser(authorizationUri.ToString())) { - messages.Enqueue("Unable to launch a browser for authorization code login. Reverting to device code login."); + WriteWarning("Unable to launch a browser for authorization code login. Reverting to device code login."); } - messages.Enqueue(message); + WriteWarning(message); using (SingleMessageTcpListener listener = new SingleMessageTcpListener(redirectUri.Port)) { @@ -154,5 +151,13 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network return CloseWindowSuccessHtml; } + + private void WriteWarning(string message) + { + if (PartnerSession.Instance.TryGetComponent("WriteWarning", out EventHandler writeWarningEvent)) + { + writeWarningEvent(this, new StreamEventArgs { Resource = message }); + } + } } } \ No newline at end of file diff --git a/src/PowerShell/Network/GraphAuthenticationProvider.cs b/src/PowerShell/Network/GraphAuthenticationProvider.cs index ba6ba65..d2975c0 100644 --- a/src/PowerShell/Network/GraphAuthenticationProvider.cs +++ b/src/PowerShell/Network/GraphAuthenticationProvider.cs @@ -22,10 +22,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network /// An instance of the class that represents the asynchronous operation. public async Task AuthenticateRequestAsync(HttpRequestMessage request) { - AuthenticationResult authResult = PartnerSession.Instance.AuthenticationFactory.Authenticate( + AuthenticationResult authResult = await PartnerSession.Instance.AuthenticationFactory.AuthenticateAsync( PartnerSession.Instance.Context.Account, PartnerSession.Instance.Context.Environment, - new[] { $"{PartnerSession.Instance.Context.Environment.GraphEndpoint}/.default" }); + new[] { $"{PartnerSession.Instance.Context.Environment.GraphEndpoint}/.default" }).ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); diff --git a/src/PowerShell/Network/RecordingTracingInterceptor.cs b/src/PowerShell/Network/RecordingTracingInterceptor.cs index ff974db..01008e2 100644 --- a/src/PowerShell/Network/RecordingTracingInterceptor.cs +++ b/src/PowerShell/Network/RecordingTracingInterceptor.cs @@ -6,45 +6,230 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network using System; using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using System.Net.Http; + using System.Text; + using System.Xml.Linq; + using Extensions; + using Newtonsoft.Json; using Rest; + /// + /// Provides useful information about operations that are performed. + /// public class RecordingTracingInterceptor : IServiceClientTracingInterceptor { - public RecordingTracingInterceptor(ConcurrentQueue queue) + /// + /// The queue where message will be enqueued for output. + /// + private readonly ConcurrentQueue messageQueue; + + /// + /// Initializes a new instance of the class. + /// + /// The queue where message will be enqueued for output. + public RecordingTracingInterceptor(ConcurrentQueue messageQueue) { - MessageQueue = queue; + messageQueue.AssertNotNull(nameof(messageQueue)); + + this.messageQueue = messageQueue; } - public ConcurrentQueue MessageQueue { get; private set; } - + /// + /// Probe configuration for the value of a setting + /// + /// The configuration source. + /// The name of the setting. + /// The value of the setting in the source. public void Configuration(string source, string name, string value) { } + /// + /// Enter a method. + /// + /// The method invocation identifier. + /// The instance with the method. + /// The name of the method. + /// The parameters provided during the method invociation. public void EnterMethod(string invocationId, object instance, string method, IDictionary parameters) { } + /// + /// Exit a method. Note: Exit will not be called in the event of an error. + /// + /// The method invocation identifier. + /// The value returned from the method invocation. public void ExitMethod(string invocationId, object returnValue) { } + /// + /// Handles the request to trace information. + /// + /// The information to trace. public void Information(string message) { - MessageQueue.Enqueue(message); + messageQueue.Enqueue(message); } + /// + /// Receive an HTTP response. + /// + /// The method invocation identifier. + /// The response from the HTTP operation. public void ReceiveResponse(string invocationId, HttpResponseMessage response) { + StringBuilder output = new StringBuilder(); + + output.AppendLine($"============================ HTTP RESPONSE ============================"); + output.AppendLine($"Status Code:{Environment.NewLine}{response.StatusCode}{Environment.NewLine}"); + output.AppendLine($"Headers:"); + + foreach (KeyValuePair> item in response.Headers.ToDictionary(h => h.Key, h => h.Value).ToArray()) + { + output.AppendLine(string.Format( + "{0,-30}: {1}", + item.Key, + string.Join(",", item.Value))); + } + + if (response.Content != null) + { + output.AppendLine(string.Empty); + output.AppendLine("Body:"); + output.AppendLine(FormatString(response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult())); + } + + messageQueue.Enqueue(output.ToString()); } + /// + /// Send an HTTP request. + /// + /// The method invocation identifier. + /// The HTTP request to be sent. public void SendRequest(string invocationId, HttpRequestMessage request) { + StringBuilder output = new StringBuilder(); + + output.AppendLine($"============================ HTTP REQUEST ============================"); + output.AppendLine($"HTTP Method:{Environment.NewLine}{request.Method}{Environment.NewLine}"); + output.AppendLine($"Absolute Uri:{Environment.NewLine}{request.RequestUri}{Environment.NewLine}"); + output.AppendLine($"Headers:"); + + foreach (KeyValuePair> item in request.Headers.Where(h => !h.Key.Equals("Authorization")).ToDictionary(h => h.Key, h => h.Value).ToArray()) + { + output.AppendLine(string.Format( + "{0,-30}: {1}", + item.Key, + string.Join(",", item.Value))); + } + + if (request.Content != null) + { + output.AppendLine(string.Empty); + output.AppendLine("Body:"); + output.AppendLine(FormatString(request.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult())); + } + + messageQueue.Enqueue(output.ToString()); } + /// + /// Provides the ability to trace an error. + /// + /// The method invocation identifier. + /// The exception that was thrown during the method invocation. public void TraceError(string invocationId, Exception exception) { } + + /// + /// Formats the content based on the type. + /// + /// The content to be formatted. + /// A string that contains the formatted content. + private static string FormatString(string content) + { + if (IsJson(content)) + { + return TryFormatJson(content); + } + else if (IsXml(content)) + { + return TryFormatXml(content); + } + + return content; + } + + /// + /// Checks if the content is JSON. + /// + /// The content to be checked. + /// true if the content is JSON; otherwise false. + public static bool IsJson(string content) + { + content = content.Trim(); + + return content.StartsWith("{", StringComparison.InvariantCultureIgnoreCase) + && content.EndsWith("}", StringComparison.InvariantCultureIgnoreCase) + || content.StartsWith("[", StringComparison.InvariantCultureIgnoreCase) + && content.EndsWith("]", StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Checks if the content is valid XML or not. + /// + /// The content to be checked. + /// true if the content is XML; otherwise false. + public static bool IsXml(string content) + { + try + { + XDocument.Parse(content); + return true; + } + catch + { + return false; + } + } + + /// + /// Formats the JSON using the appropriate indentation. + /// + /// The JSON to be formatted. + /// The formatted JSON string. + public static string TryFormatJson(string content) + { + try + { + return JsonConvert.SerializeObject(JsonConvert.DeserializeObject(content), Formatting.Indented); + } + catch + { + return content; + } + } + + /// + /// Formats the XML using the appropriate indentation. + /// + /// The XML to be formatted. + /// The formatted XML string. + public static string TryFormatXml(string content) + { + try + { + return XDocument.Parse(content).ToString(); + } + catch + { + return content; + } + } } -} +} \ No newline at end of file diff --git a/src/PowerShell/PartnerCenter.psd1 b/src/PowerShell/PartnerCenter.psd1 index bcfea4b..43fa3fc 100644 --- a/src/PowerShell/PartnerCenter.psd1 +++ b/src/PowerShell/PartnerCenter.psd1 @@ -11,7 +11,7 @@ RootModule = 'PartnerCenter.psm1' # Version number of this module. - ModuleVersion = '2.0.1911.6' + ModuleVersion = '3.0.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' @@ -80,8 +80,10 @@ 'Disconnect-PartnerCenter', 'Get-PartnerAgreementDetail', 'Get-PartnerAgreementDocument', + 'Get-PartnerAgreementStatus', 'Get-PartnerAuditRecord', 'Get-PartnerAzureBillingAccount', + 'Get-PartnerAzureBillingPolicy', 'Get-PartnerAzureBillingProfile', 'Get-PartnerAzureRateCard', 'Get-PartnerBillingProfile', @@ -173,6 +175,7 @@ 'Remove-PartnerSandboxCustomer', 'Resolve-PartnerError', 'Restore-PartnerCustomerUser', + 'Set-PartnerAzureBillingPolicy', 'Set-PartnerAzureSubscription', 'Set-PartnerBillingProfile', 'Set-PartnerCustomer', @@ -226,7 +229,7 @@ ReleaseNotes = '' # Prerelease string of this module - # Prerelease = 'preview' + Prerelease = 'preview' # Flag to indicate whether the module requires explicit user acceptance for install/update # RequireLicenseAcceptance = $false diff --git a/src/PowerShell/PowerShell.csproj b/src/PowerShell/PowerShell.csproj index 26d2454..a34f424 100644 --- a/src/PowerShell/PowerShell.csproj +++ b/src/PowerShell/PowerShell.csproj @@ -18,7 +18,7 @@ false $(RepoArtifacts)$(Configuration)\ $(OutputPath) - 2.0.1911.6 + 3.0.0 latest MIT @@ -30,10 +30,10 @@ - - + + - + diff --git a/src/PowerShell/Utilities/ConcurrencyTaskScheduler.cs b/src/PowerShell/Utilities/ConcurrencyTaskScheduler.cs new file mode 100644 index 0000000..53e7f01 --- /dev/null +++ b/src/PowerShell/Utilities/ConcurrencyTaskScheduler.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Utilities +{ + using System; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.Management.Automation; + using System.Threading; + using System.Threading.Tasks; + using Extensions; + using Models.Authentication; + using Properties; + + /// + /// Provides the ability to schedule task and limit the concurrency. + /// + public class ConcurrencyTaskScheduler : IDisposable + { + /// + /// The cancellation used by other objects or threads to receive notice of cancellation. + /// + private readonly CancellationToken cancellationToken; + + /// + /// The queue of tasks to be invoked. + /// + private readonly ConcurrentQueue>> taskQueue; + + /// + /// The queue of status for each task. + /// + private readonly ConcurrentDictionary taskStatus; + + /// + /// The synchronization primitive that is signaled when its count reaches zero. + /// + private readonly CountdownEvent taskCounter; + + /// + /// The maxium number of tasks that can be running at once. + /// + private readonly int maxConcurrency; + + /// + /// A flag that indicates whether or not this object has been disposed. + /// + private bool disposed; + + /// + /// A flag that indicates whether or not the first wait has occurred. + /// + private bool isFirstWait; + + /// + /// The number of active tasks. + /// + private long activeTaskCount = 0; + + /// + /// The number of failed tasks. + /// + private long failedTaskCount = 0; + + /// + /// The number of finished tasks. + /// + private long finishedTaskCount = 0; + + /// + /// The total number of tasks. + /// + private long totalTaskCount = 0; + + /// + /// Initializes a new instance of the class. + /// + /// The maxium number of tasks that can be running at once. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public ConcurrencyTaskScheduler(int maxConcurrency, CancellationToken cancellationToken) + { + this.cancellationToken = cancellationToken; + this.maxConcurrency = maxConcurrency; + + isFirstWait = true; + taskCounter = new CountdownEvent(1); + taskQueue = new ConcurrentQueue>>(); + taskStatus = new ConcurrentDictionary(); + } + + /// + /// Occurs when an error has been encountered during the execution of a task. + /// + /// The resource that triggered the event. + /// The arguments for the event. + public delegate void ErrorEventHandler(object sender, TaskExceptionEventArgs e); + + /// + /// Occurs when an error has been encountered during the execution of a task. + /// + public event ErrorEventHandler OnError; + + /// + /// Checks if all tasks have completed. + /// + /// The number of milliseconds to wait, or System.Threading.Timeout.Infinite(-1) to wait indefinitely. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// true if all tasks have completed; otherwise, false. + /// + public bool CheckForComplete(int millisecondsTimeout, CancellationToken cancellationToken) + { + if (isFirstWait) + { + isFirstWait = false; + taskCounter.Signal(); + } + + return taskCounter.Wait(millisecondsTimeout, cancellationToken); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (disposed) + { + return; + } + + if (disposing) + { + taskCounter?.Dispose(); + } + + disposed = true; + } + + /// + /// Queues the specified work to run on the thread pool. + /// + /// The work to execute asynchronously + public void RunTask(Func function) + { + RunTask(function, false); + } + + /// + /// Queues the specified work to run on the thread pool. + /// + /// The work to execute asynchronously + public void RunTask(Func function, bool validateConnected) + { + long taskId; + + function.AssertNotNull(nameof(function)); + + if (cancellationToken.IsCancellationRequested) + { + return; + } + + if (validateConnected) + { + if (PartnerSession.Instance.Context == null) + { + throw new PSInvalidOperationException(Resources.RunConnectPartnerCenter); + } + } + + taskId = totalTaskCount; + Interlocked.Increment(ref totalTaskCount); + + taskCounter.AddCount(); + + if (Interlocked.Read(ref activeTaskCount) < maxConcurrency) + { + RunConcurrentTaskAsync(taskId, function()); + } + else + { + taskQueue.Enqueue(new Tuple>(taskId, function)); + } + } + + private async void RunConcurrentTaskAsync(long taskId, Task task) + { + Interlocked.Increment(ref activeTaskCount); + + try + { + taskStatus.TryAdd(taskId, false); + await task.ConfigureAwait(false); + + Interlocked.Increment(ref finishedTaskCount); + } + catch (Exception ex) + { + Interlocked.Increment(ref failedTaskCount); + + if (OnError != null) + { + TaskExceptionEventArgs eventArgs = new TaskExceptionEventArgs(ex); + + try + { + OnError(this, eventArgs); + } + catch (Exception devException) + { + Debug.Fail(devException.Message); + } + } + } + finally + { + taskStatus.TryUpdate(taskId, true, false); + } + + Interlocked.Decrement(ref activeTaskCount); + + if (disposed == false) + { + taskCounter?.Signal(); + RunRemainingTask(); + } + } + + private void RunRemainingTask() + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + taskQueue.TryDequeue(out Tuple> item); + + if (item != null) + { + RunConcurrentTaskAsync(item.Item1, item.Item2()); + } + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Utilities/TaskExceptionEventArgs.cs b/src/PowerShell/Utilities/TaskExceptionEventArgs.cs new file mode 100644 index 0000000..b6c3ccb --- /dev/null +++ b/src/PowerShell/Utilities/TaskExceptionEventArgs.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Store.PartnerCenter.PowerShell.Utilities +{ + using System; + + /// + /// The event arguments an exception that was thrown during the execution of task. + /// + public class TaskExceptionEventArgs : EventArgs + { + /// + /// Gets the exception that was thrown. + /// + public Exception Exception { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The exception that was thrown. + public TaskExceptionEventArgs(Exception e) + { + Exception = e; + } + } +} \ No newline at end of file diff --git a/src/PowerShell/Validations/AddressValidator.cs b/src/PowerShell/Validations/AddressValidator.cs index 261a302..03c5b70 100644 --- a/src/PowerShell/Validations/AddressValidator.cs +++ b/src/PowerShell/Validations/AddressValidator.cs @@ -58,24 +58,22 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Validations resource.AssertNotNull(nameof(resource)); - if (resource.Country.Equals(ChinaCountryCode, StringComparison.InvariantCultureIgnoreCase) || - resource.Country.Equals(MexicoCountryCode, StringComparison.InvariantCultureIgnoreCase) || - resource.Country.Equals(UnitedStatesCountryCode, StringComparison.InvariantCultureIgnoreCase)) + debugAction("Requesting country validation services from the partner service."); + validationRules = partner.CountryValidationRules.ByCountry(resource.Country).GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + + if (validationRules.IsCityRequired && string.IsNullOrEmpty(resource.City)) { - debugAction("Requesting country validation services from the partner service."); - validationRules = partner.CountryValidationRules.ByCountry(resource.Country).GetAsync().GetAwaiter().GetResult(); + throw new ValidationException(Resources.CityRequiredError); + } - if (validationRules.IsCityRequired && string.IsNullOrEmpty(resource.City)) - { - throw new ValidationException(Resources.CityRequiredError); - } + if (validationRules.IsPostalCodeRequired && string.IsNullOrEmpty(resource.PostalCode)) + { + throw new ValidationException(Resources.PostalCoderequiredError); + } - if (validationRules.IsPostalCodeRequired && string.IsNullOrEmpty(resource.PostalCode)) - { - throw new ValidationException(Resources.PostalCoderequiredError); - } - - if (validationRules.IsStateRequired && string.IsNullOrEmpty(resource.State)) + if (validationRules.IsStateRequired) + { + if (string.IsNullOrEmpty(resource.State)) { throw new ValidationException(Resources.StateRequiredError); } @@ -91,21 +89,38 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Validations string.Join(",", validationRules.SupportedStatesList))); } } - - if (!string.IsNullOrEmpty(validationRules.PhoneNumberRegex) - && !string.IsNullOrEmpty(resource.PhoneNumber) && - !Regex.Match(resource.PhoneNumber, validationRules.PhoneNumberRegex).Success) - { - throw new ValidationException( - string.Format( - CultureInfo.CurrentCulture, - Resources.InvalidPhoneFormatError, - resource.PhoneNumber)); - } } + if (!string.IsNullOrEmpty(validationRules.PhoneNumberRegex) + && !string.IsNullOrEmpty(resource.PhoneNumber) && + !Regex.Match(resource.PhoneNumber, validationRules.PhoneNumberRegex).Success) + { + throw new ValidationException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidPhoneFormatError, + resource.PhoneNumber)); + } + + //if (string.IsNullOrEmpty(resource.Country) && !string.IsNullOrEmpty(resource.Region)) + //{ + // debugAction("The country parameter must be specified to perform address validation. Since it was not provided this operation will not be performed a default value of true will be returned."); + // return true; + //} + + //if (string.IsNullOrEmpty(resource.Country)) + //{ + // debugAction("The country parameter must be specifed to perform address validation. Since it was not provided this operation will be performed and a value of false will be returned."); + // return false; + //} + + //if (resource.Country.Equals(ChinaCountryCode, StringComparison.InvariantCultureIgnoreCase) || + // resource.Country.Equals(MexicoCountryCode, StringComparison.InvariantCultureIgnoreCase) || + // resource.Country.Equals(UnitedStatesCountryCode, StringComparison.InvariantCultureIgnoreCase)) + //{ debugAction("Checking if the address is valid using the partner service."); - return partner.Validations.IsAddressValidAsync(resource).GetAwaiter().GetResult(); + return partner.Validations.IsAddressValidAsync(resource).ConfigureAwait(false).GetAwaiter().GetResult(); + // } } } } \ No newline at end of file diff --git a/src/Subscription/OperationsExtensions.cs b/src/Subscription/OperationsExtensions.cs index be9bd56..9f319ac 100644 --- a/src/Subscription/OperationsExtensions.cs +++ b/src/Subscription/OperationsExtensions.cs @@ -27,7 +27,7 @@ namespace Microsoft.Azure.Management.Profiles.Subscription /// public static OperationListResult List(this IOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Subscription/SubscriptionOperationsExtensions.cs b/src/Subscription/SubscriptionOperationsExtensions.cs index 572688e..5690dd2 100644 --- a/src/Subscription/SubscriptionOperationsExtensions.cs +++ b/src/Subscription/SubscriptionOperationsExtensions.cs @@ -27,7 +27,7 @@ namespace Microsoft.Azure.Management.Profiles.Subscription /// public static SubscriptionOperationListResult List(this ISubscriptionOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Subscription/SubscriptionsOperationsExtensions.cs b/src/Subscription/SubscriptionsOperationsExtensions.cs index f5a800a..e9c733c 100644 --- a/src/Subscription/SubscriptionsOperationsExtensions.cs +++ b/src/Subscription/SubscriptionsOperationsExtensions.cs @@ -215,7 +215,7 @@ namespace Microsoft.Azure.Management.Profiles.Subscription /// public static IPage List(this ISubscriptionsOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/src/Subscription/TenantsOperationsExtensions.cs b/src/Subscription/TenantsOperationsExtensions.cs index 8d906ff..c7a3347 100644 --- a/src/Subscription/TenantsOperationsExtensions.cs +++ b/src/Subscription/TenantsOperationsExtensions.cs @@ -28,7 +28,7 @@ namespace Microsoft.Azure.Management.Profiles.Subscription /// public static IPage List(this ITenantsOperations operations) { - return operations.ListAsync().GetAwaiter().GetResult(); + return operations.ListAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// diff --git a/test/PowerShell.UnitTests/Factories/MockAuthenticationFactory.cs b/test/PowerShell.UnitTests/Factories/MockAuthenticationFactory.cs index 32d9081..ca48f0e 100644 --- a/test/PowerShell.UnitTests/Factories/MockAuthenticationFactory.cs +++ b/test/PowerShell.UnitTests/Factories/MockAuthenticationFactory.cs @@ -6,6 +6,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories using System; using System.Collections.Generic; using System.Threading; + using System.Threading.Tasks; using Identity.Client; using PowerShell.Factories; using PowerShell.Models.Authentication; @@ -15,23 +16,21 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories /// public class MockAuthenticationFactory : IAuthenticationFactory { - /// - /// Acquires the security token from the authority. - /// - /// The result from the authentication request. - public AuthenticationResult Authenticate(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message, Action promptAction = null, Action debugAction = null, CancellationToken cancellationToken = default) + public async Task AuthenticateAsync(PartnerAccount account, PartnerEnvironment environment, IEnumerable scopes, string message = null, CancellationToken cancellationToken = default) { - return new AuthenticationResult( - "STUB_TOKEN", - true, - "uniquedId", - DateTimeOffset.UtcNow.AddHours(1), - DateTimeOffset.UtcNow.AddHours(1), - "xxxx-xxxx-xxxx-xxxx", - null, - "STUB_TOKEN", - scopes); - } + await Task.CompletedTask; + return new AuthenticationResult( + "STUB_TOKEN", + true, + "uniquedId", + DateTimeOffset.UtcNow.AddHours(1), + DateTimeOffset.UtcNow.AddHours(1), + "xxxx-xxxx-xxxx-xxxx", + null, + "STUB_TOKEN", + scopes, + Guid.Empty); + } } } \ No newline at end of file diff --git a/test/PowerShell.UnitTests/Factories/MockClientFactory.cs b/test/PowerShell.UnitTests/Factories/MockClientFactory.cs index 7c5451b..7db039a 100644 --- a/test/PowerShell.UnitTests/Factories/MockClientFactory.cs +++ b/test/PowerShell.UnitTests/Factories/MockClientFactory.cs @@ -4,10 +4,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories { using System; + using System.Threading.Tasks; using Graph; using Network; using PowerShell.Factories; - using PowerShell.Network; using Rest; /// @@ -54,7 +54,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories { if (graphServiceClient == null) { - graphServiceClient = new GraphServiceClient(null, new HttpProvider(new CancelRetryHandler(3, TimeSpan.FromSeconds(10)) + graphServiceClient = new GraphServiceClient(null, new HttpProvider(new RetryDelegatingHandler { InnerHandler = httpMockHandler }, false, null)); @@ -67,7 +67,16 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories /// Creates a new instance of the object used to interface with Partner Center. /// /// An instance of the class. - public IPartner CreatePartnerOperations() + public virtual IPartner CreatePartnerOperations() + { + return CreatePartnerOperationsAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Creates a new instance of the object used to interface with Partner Center. + /// + /// An instance of the class. + public virtual async Task CreatePartnerOperationsAsync() { if (partnerOperations == null) { @@ -76,6 +85,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories httpMockHandler); } + await Task.CompletedTask.ConfigureAwait(false); + return partnerOperations; } @@ -86,7 +97,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories /// Scopes requested to access a protected service. /// The identifier for the tenant. /// An instance of a service client that is connected to a specific service. - public TClient CreateServiceClient(string[] scopes, string tenantId = null) where TClient : ServiceClient + public Task CreateServiceClientAsync(string[] scopes, string tenantId = null) where TClient : ServiceClient { throw new NotImplementedException(); } diff --git a/test/PowerShell.UnitTests/Network/HttpMockHandler.cs b/test/PowerShell.UnitTests/Network/HttpMockHandler.cs index 261cadd..4009699 100644 --- a/test/PowerShell.UnitTests/Network/HttpMockHandler.cs +++ b/test/PowerShell.UnitTests/Network/HttpMockHandler.cs @@ -163,12 +163,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network }; processed = new List(); - -#if FullNetFx - location = Path.Combine(Assembly.GetCallingAssembly().Location, SessionsDirectory); -#else location = Path.Combine(AppContext.BaseDirectory, SessionsDirectory); -#endif foreach (string fileName in Directory.GetFiles(location)) { diff --git a/test/PowerShell.UnitTests/PowerShell.UnitTests.csproj b/test/PowerShell.UnitTests/PowerShell.UnitTests.csproj index 8e19dac..acc81ba 100644 --- a/test/PowerShell.UnitTests/PowerShell.UnitTests.csproj +++ b/test/PowerShell.UnitTests/PowerShell.UnitTests.csproj @@ -7,7 +7,7 @@ false Microsoft.Store.PartnerCenter.PowerShell.UnitTests Microsoft.Store.PartnerCenter.PowerShell.UnitTests - 2.0.1911.6 + 3.0.0 en-US MIT latest diff --git a/tools/HelpGeneration/Exceptions/ValidateHelpIssues.csv b/tools/HelpGeneration/Exceptions/ValidateHelpIssues.csv index ebd878c..e69de29 100644 --- a/tools/HelpGeneration/Exceptions/ValidateHelpIssues.csv +++ b/tools/HelpGeneration/Exceptions/ValidateHelpIssues.csv @@ -1 +0,0 @@ -Target,Description \ No newline at end of file diff --git a/tools/HelpGeneration/HelpGeneration.psm1 b/tools/HelpGeneration/HelpGeneration.psm1 index 8362ddb..fd64253 100644 --- a/tools/HelpGeneration/HelpGeneration.psm1 +++ b/tools/HelpGeneration/HelpGeneration.psm1 @@ -10,11 +10,11 @@ function New-PartnerCenterMarkdownHelp ) $HelpFolder = Get-Item $HelpFolderPath - $ModuleFolder = $HelpFolder.Parent - $ModuleFolderPath = $ModuleFolder.FullName + $ModuleFolder = $HelpFolder.Parent.Parent + $ModuleFolderPath = "$($ModuleFolder.FullName)\src\PowerShell\" $NewHelpFolderPath = "$ModuleFolderPath\temp_help" - $psd1 = Get-ChildItem $ModuleFolderPath | where { $_.Name -eq "$($ModuleFolder.Name).psd1" } + $psd1 = Get-ChildItem $ModuleFolderPath | where { $_.Name -eq "PartnerCenter.psd1" } Import-Module $psd1.FullName -Scope Global