From 457aa6396e7491caefb580a1895b2f74188a89bf Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Tue, 26 May 2020 19:09:19 -0700 Subject: [PATCH 1/8] Add script to generate profiles per module. --- tools/ProfileGenerator.ps1 | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tools/ProfileGenerator.ps1 diff --git a/tools/ProfileGenerator.ps1 b/tools/ProfileGenerator.ps1 new file mode 100644 index 0000000000..da0b44b6ba --- /dev/null +++ b/tools/ProfileGenerator.ps1 @@ -0,0 +1,79 @@ +Param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()][string] $OpenApiDocsDirectory, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()][string] $ProfilesDirectory +) +try { + # Install dependencies. + if (!(Get-Module "powershell-yaml" -ListAvailable -ErrorAction SilentlyContinue)){ + Install-Module "powershell-yaml" + } + + if(!(Test-Path -Path $OpenApiDocsDirectory)){ + New-Item -Path $OpenApiDocsDirectory -ItemType Directory + } + + if(!(Test-Path -Path $ProfilesDirectory)){ + New-Item -Path $ProfilesDirectory -ItemType Directory + } + + $apiList = Get-ChildItem -Path $OpenApiDocsDirectory + $specs = @() + $foundMultipleAPIs = $false + if ($apiList.Count -gt 1) { + $foundMultipleAPIs = $true + } + foreach ($api in $apiList) { + # $apiVersion = $api.Name + $openApiDocs = Get-ChildItem -File -Filter "*.yml" -Path $api.FullName + Write-Host "Parsing openAPI docs..." -ForegroundColor Green + $openApiDocs = $openApiDocs[1] + foreach ($openApiDoc in $openApiDocs){ + # get paths + $allPaths = @() + $moduleName = $openApiDoc.BaseName + $openApiContent = Get-Content -Path $openApiDoc.FullName | ConvertFrom-Yaml + if ($openApiContent.openapi && $openApiContent.info.version){ + $apiVersion = $openApiContent.info.version + # Get sovreign cloud. + $profileName = $apiVersion + + # Get paths. + foreach ($path in $openApiContent.paths.keys) { + $allPaths += @{endpoint= $path; apiVersion = $apiVersion; originalLocation = ($openApiDoc.FullName | Resolve-Path -Relative) -replace "^.\\|\\", "/"} + } + # Get crawl data. + Write-Host "Crawling paths for resources and operations for $moduleName ..." -ForegroundColor Green + $crawlResult = @{resources= @(); operations = @{}} + foreach ($path in $allPaths) { + $crawlResult.operations[$path.endpoint] = (@{apiVersion = $path.apiVersion; originalLocation = $path.originalLocation}) + } + + $ModuleProfilesDirectory = "$ProfilesDirectory/$moduleName" + if(!(Test-Path -Path $ModuleProfilesDirectory)){ + New-Item -Path $ModuleProfilesDirectory -ItemType Directory + } + $telemetryDir = Join-Path $ModuleProfilesDirectory "crawl-log-$profileName.json" + Set-Content -Path $telemetryDir -Value ($crawlResult | ConvertTo-Json) + + # Get profile. + $profile = @{resources = @{}; operations = @{}} + foreach ($operation in $crawlResult.operations.keys) { + $profile.operations[$operation] = $crawlResult.operations[$operation].apiVersion + } + Write-Host ($profile | ConvertTo-Json) + $profileReadMeContent = @" +# Microsoft Graph $profileName Profile +``````` yaml +$($profile | ConvertTo-Yaml) +``````` +"@ + $telemetryDir = Join-Path $ModuleProfilesDirectory "readme.md" + Set-Content -Path $telemetryDir -Value $profileReadMeContent + } + } + } +} +catch { + Write-Error "Failed to generate profile." + Write-Error $_ +} \ No newline at end of file From 108158cdaebdd760cf186a62fa4430f174587a22 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Wed, 27 May 2020 11:21:39 -0700 Subject: [PATCH 2/8] Generate national cloud aware profile definitions. --- tools/GetNationalCloud.ps1 | 23 +++++++++++ tools/ProfileGenerator.ps1 | 79 ++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 tools/GetNationalCloud.ps1 diff --git a/tools/GetNationalCloud.ps1 b/tools/GetNationalCloud.ps1 new file mode 100644 index 0000000000..36de75e07a --- /dev/null +++ b/tools/GetNationalCloud.ps1 @@ -0,0 +1,23 @@ +Param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()][string] $apiVersion, + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()][string] $serverUrl +) + +Write-Host $serverUrl +switch -regex ($serverUrl) { + "https://graph.microsoft.us/*" { + return "USGOV-$apiVersion" + } + "https://dod-graph.microsoft.us/*" { + return "USGOV-DOD-$apiVersion" + } + "https://microsoftgraph.chinacloudapi.cn/*" { + return "China-$apiVersion" + } + "https://graph.microsoft.de/*" { + return "Germany-$apiVersion" + } + Default { + return $apiVersion + } +} \ No newline at end of file diff --git a/tools/ProfileGenerator.ps1 b/tools/ProfileGenerator.ps1 index da0b44b6ba..2a0efbf200 100644 --- a/tools/ProfileGenerator.ps1 +++ b/tools/ProfileGenerator.ps1 @@ -15,63 +15,92 @@ try { if(!(Test-Path -Path $ProfilesDirectory)){ New-Item -Path $ProfilesDirectory -ItemType Directory } + $GetNationalCloudPS1 = Join-Path $PSScriptRoot ".\GetNationalCloud.ps1" -Resolve - $apiList = Get-ChildItem -Path $OpenApiDocsDirectory - $specs = @() - $foundMultipleAPIs = $false - if ($apiList.Count -gt 1) { - $foundMultipleAPIs = $true - } - foreach ($api in $apiList) { + foreach ($api in (Get-ChildItem -Path $OpenApiDocsDirectory)) { # $apiVersion = $api.Name $openApiDocs = Get-ChildItem -File -Filter "*.yml" -Path $api.FullName - Write-Host "Parsing openAPI docs..." -ForegroundColor Green - $openApiDocs = $openApiDocs[1] + Write-Host "Parsing $($api.Name) openAPI docs..." -ForegroundColor Yellow foreach ($openApiDoc in $openApiDocs){ - # get paths $allPaths = @() $moduleName = $openApiDoc.BaseName + # Create required directories. + $ModuleProfilesDirectory = "$ProfilesDirectory/$moduleName" + if(!(Test-Path -Path $ModuleProfilesDirectory)){ + New-Item -Path $ModuleProfilesDirectory -ItemType Directory + } + + $ModuleProfilesDefinitionsDirectory = "$ModuleProfilesDirectory/definitions" + if(!(Test-Path -Path $ModuleProfilesDefinitionsDirectory)){ + New-Item -Path $ModuleProfilesDefinitionsDirectory -ItemType Directory + } + $openApiContent = Get-Content -Path $openApiDoc.FullName | ConvertFrom-Yaml if ($openApiContent.openapi && $openApiContent.info.version){ $apiVersion = $openApiContent.info.version - # Get sovreign cloud. - $profileName = $apiVersion + # Get national cloud profile. + $profileName = & $GetNationalCloudPS1 -apiVersion $apiVersion -serverUrl $openApiContent.servers.url # Get paths. foreach ($path in $openApiContent.paths.keys) { $allPaths += @{endpoint= $path; apiVersion = $apiVersion; originalLocation = ($openApiDoc.FullName | Resolve-Path -Relative) -replace "^.\\|\\", "/"} } # Get crawl data. - Write-Host "Crawling paths for resources and operations for $moduleName ..." -ForegroundColor Green + Write-Host "Crawling '$moduleName' paths for resources and operations ..." -ForegroundColor Green $crawlResult = @{resources= @(); operations = @{}} foreach ($path in $allPaths) { $crawlResult.operations[$path.endpoint] = (@{apiVersion = $path.apiVersion; originalLocation = $path.originalLocation}) } - - $ModuleProfilesDirectory = "$ProfilesDirectory/$moduleName" - if(!(Test-Path -Path $ModuleProfilesDirectory)){ - New-Item -Path $ModuleProfilesDirectory -ItemType Directory - } $telemetryDir = Join-Path $ModuleProfilesDirectory "crawl-log-$profileName.json" Set-Content -Path $telemetryDir -Value ($crawlResult | ConvertTo-Json) + Write-Host "Telemetry written at $telemetryDir" -ForegroundColor Blue # Get profile. - $profile = @{resources = @{}; operations = @{}} + $profile = @{resources = @{}; operations = @{}} foreach ($operation in $crawlResult.operations.keys) { $profile.operations[$operation] = $crawlResult.operations[$operation].apiVersion } - Write-Host ($profile | ConvertTo-Json) - $profileReadMeContent = @" + $profilesNode = @{profiles = @{ $profileName = $profile}} + $profilesInYaml = $profilesNode | ConvertTo-Yaml + $profileReadMeContent = @" # Microsoft Graph $profileName Profile + +> see https://aka.ms/autorest + ``````` yaml -$($profile | ConvertTo-Yaml) -``````` +$profilesInYaml +``````` "@ - $telemetryDir = Join-Path $ModuleProfilesDirectory "readme.md" - Set-Content -Path $telemetryDir -Value $profileReadMeContent + $profileReadMeDir = Join-Path $ModuleProfilesDefinitionsDirectory "$profileName.md" + Set-Content -Path $profileReadMeDir -Value $profileReadMeContent + Write-Host "Profile '$profileName' written at $profileReadMeDir" -ForegroundColor Blue } } } + + # Get all profile defintions of a module and generate a single readme. + foreach ($moduleItem in (Get-ChildItem $ProfilesDirectory)) { + $definitionsRelativePaths = @{ require = @()} + foreach ($moduleDefinition in (Get-ChildItem -Filter *.md -Path "$($moduleItem.FullName)/definitions")) { + $definitionsRelativePaths.require += '$(this-folder)/definitions/'+ $moduleDefinition.Name + } + + $definitionsRelativePathsAsYaml = ($definitionsRelativePaths | ConvertTo-Yaml) + $moduleReadMeContent = @" +# Microsoft Graph $($moduleItem.Name) Profiles + +> see https://aka.ms/autorest + +> The files under this directory are the profile definitions used by autorest. + +``````` yaml +$definitionsRelativePathsAsYaml +``````` +"@ + $moduleReadMeDir = Join-Path $moduleItem.FullName "readme.md" + Set-Content -Path $moduleReadMeDir -Value $moduleReadMeContent + Write-Host "Regenerated profiles readme.md at $moduleReadMeDir" -ForegroundColor Yellow + } } catch { Write-Error "Failed to generate profile." From 3ef5c13d3f54db18f160cba1ed3ec2d017f3bfa1 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Wed, 27 May 2020 18:46:17 -0700 Subject: [PATCH 3/8] Consume profile definitions when generating modules. --- src/Beta/Users.User/Users.User/readme.md | 3 ++- src/readme.graph.md | 9 ++++++++ tools/Custom/Module.cs | 1 + ...fileGenerator.ps1 => GenerateProfiles.ps1} | 21 ++++++++++++++++--- tools/GetNationalCloud.ps1 | 1 - tools/Templates/readme.md | 4 ++-- 6 files changed, 32 insertions(+), 7 deletions(-) rename tools/{ProfileGenerator.ps1 => GenerateProfiles.ps1} (87%) diff --git a/src/Beta/Users.User/Users.User/readme.md b/src/Beta/Users.User/Users.User/readme.md index f83779b1fa..6880dacf40 100644 --- a/src/Beta/Users.User/Users.User/readme.md +++ b/src/Beta/Users.User/Users.User/readme.md @@ -27,10 +27,11 @@ For information on how to develop for `Microsoft.Graph.Users.User`, see [how-to. ``` yaml require: - $(this-folder)/../../../readme.graph.md + - $(this-folder)/../../../../profiles/$(title)/readme.md title: $(service-name) subject-prefix: '' -input-file: $(spec-doc-repo)/$(title).yml ``` + ### Versioning ``` yaml diff --git a/src/readme.graph.md b/src/readme.graph.md index 9a5451e343..fffc0fe18f 100644 --- a/src/readme.graph.md +++ b/src/readme.graph.md @@ -37,6 +37,15 @@ clear-output-folder: true output-folder: . ``` +> Profiles + +``` yaml +tag: all-api-versions +profile: + - v1.0 + - v1.0-beta +``` + > Custom Directives ``` yaml diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs index 4a0e0f6ebc..8806c9623d 100644 --- a/tools/Custom/Module.cs +++ b/tools/Custom/Module.cs @@ -13,6 +13,7 @@ namespace Microsoft.Graph.PowerShell public partial class Module { + public string ProfileName { get; set; } = "v1.0-beta"; partial void BeforeCreatePipeline(System.Management.Automation.InvocationInfo invocationInfo, ref Runtime.HttpPipeline pipeline) { pipeline = new Runtime.HttpPipeline(new Runtime.HttpClientFactory(HttpHelpers.GetGraphHttpClient())); diff --git a/tools/ProfileGenerator.ps1 b/tools/GenerateProfiles.ps1 similarity index 87% rename from tools/ProfileGenerator.ps1 rename to tools/GenerateProfiles.ps1 index 2a0efbf200..e0201fc96d 100644 --- a/tools/ProfileGenerator.ps1 +++ b/tools/GenerateProfiles.ps1 @@ -17,13 +17,15 @@ try { } $GetNationalCloudPS1 = Join-Path $PSScriptRoot ".\GetNationalCloud.ps1" -Resolve + $openApiFiles = @{} foreach ($api in (Get-ChildItem -Path $OpenApiDocsDirectory)) { - # $apiVersion = $api.Name $openApiDocs = Get-ChildItem -File -Filter "*.yml" -Path $api.FullName Write-Host "Parsing $($api.Name) openAPI docs..." -ForegroundColor Yellow foreach ($openApiDoc in $openApiDocs){ $allPaths = @() $moduleName = $openApiDoc.BaseName + $openApiRelativePath = ($openApiDoc.FullName | Resolve-Path -Relative) -replace "^.\\|\\", "/" + $openApiFiles[$moduleName] += @('$(this-folder)../../'+ $openApiRelativePath) # Create required directories. $ModuleProfilesDirectory = "$ProfilesDirectory/$moduleName" if(!(Test-Path -Path $ModuleProfilesDirectory)){ @@ -43,7 +45,7 @@ try { # Get paths. foreach ($path in $openApiContent.paths.keys) { - $allPaths += @{endpoint= $path; apiVersion = $apiVersion; originalLocation = ($openApiDoc.FullName | Resolve-Path -Relative) -replace "^.\\|\\", "/"} + $allPaths += @{endpoint= $path; apiVersion = $apiVersion; originalLocation = $openApiRelativePath} } # Get crawl data. Write-Host "Crawling '$moduleName' paths for resources and operations ..." -ForegroundColor Green @@ -84,8 +86,11 @@ $profilesInYaml foreach ($moduleDefinition in (Get-ChildItem -Filter *.md -Path "$($moduleItem.FullName)/definitions")) { $definitionsRelativePaths.require += '$(this-folder)/definitions/'+ $moduleDefinition.Name } - $definitionsRelativePathsAsYaml = ($definitionsRelativePaths | ConvertTo-Yaml) + + $inputFiles = @{} + $inputFiles["input-file"] = $openApiFiles[$moduleItem.Name] + $moduleReadMeContent = @" # Microsoft Graph $($moduleItem.Name) Profiles @@ -96,6 +101,16 @@ $profilesInYaml ``````` yaml $definitionsRelativePathsAsYaml ``````` + +## Multi-API/Profile support for AutoRest v3 generators + +AutoRest V3 generators require the use of `--tag=all-api-versions` to select api files. + +This block is updated by an automatic script. Edits may be lost! + +``````` yaml `$(tag) == 'all-api-versions' /* autogenerated */ +$($inputFiles | ConvertTo-Yaml) +``````` "@ $moduleReadMeDir = Join-Path $moduleItem.FullName "readme.md" Set-Content -Path $moduleReadMeDir -Value $moduleReadMeContent diff --git a/tools/GetNationalCloud.ps1 b/tools/GetNationalCloud.ps1 index 36de75e07a..4abd4053dc 100644 --- a/tools/GetNationalCloud.ps1 +++ b/tools/GetNationalCloud.ps1 @@ -3,7 +3,6 @@ Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()][string] $serverUrl ) -Write-Host $serverUrl switch -regex ($serverUrl) { "https://graph.microsoft.us/*" { return "USGOV-$apiVersion" diff --git a/tools/Templates/readme.md b/tools/Templates/readme.md index b966e71e7d..8887d105a1 100644 --- a/tools/Templates/readme.md +++ b/tools/Templates/readme.md @@ -6,14 +6,14 @@ ``` yaml require: - $(this-folder)/../../../readme.graph.md + - $(this-folder)/../../../../profiles/$(title)/readme.md title: $(service-name) subject-prefix: '' -input-file: $(spec-doc-repo)/$(title).yml ``` ### Versioning ``` yaml -module-version: 0.5.0 +module-version: 0.5.2 release-notes: See https://aka.ms/GraphPowerShell-Release. ``` From cc723f0a24f1a9abd377e144cd7ace555e5ccbe7 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Fri, 29 May 2020 15:58:10 -0700 Subject: [PATCH 4/8] Add Get-MgProfile and Select-MgProfile cmdlets. --- .../Authentication/Cmdlets/GetMGContext.cs | 2 +- .../Authentication/Cmdlets/GetMgProfile.cs | 50 ++++++++++++ .../Authentication/Cmdlets/SelectMgProfile.cs | 55 +++++++++++++ .../Authentication/Common/GraphProfile.cs | 62 ++++++++++++++ .../Authentication/Constants.cs | 1 + .../Extensions/IEnumerableExtensions.cs | 25 ++++++ .../Extensions/PSCmdletExtensions.cs | 72 +++++++++++++++++ .../Extensions/StringExtensions.cs | 40 ++++++++++ .../Models/PSGraphServiceProfile.cs | 80 +++++++++++++++++++ 9 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/Authentication/Authentication/Cmdlets/GetMgProfile.cs create mode 100644 src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs create mode 100644 src/Authentication/Authentication/Common/GraphProfile.cs create mode 100644 src/Authentication/Authentication/Extensions/IEnumerableExtensions.cs create mode 100644 src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs create mode 100644 src/Authentication/Authentication/Extensions/StringExtensions.cs create mode 100644 src/Authentication/Authentication/Models/PSGraphServiceProfile.cs diff --git a/src/Authentication/Authentication/Cmdlets/GetMGContext.cs b/src/Authentication/Authentication/Cmdlets/GetMGContext.cs index 05942a68ed..3a501a25d7 100644 --- a/src/Authentication/Authentication/Cmdlets/GetMGContext.cs +++ b/src/Authentication/Authentication/Cmdlets/GetMGContext.cs @@ -8,7 +8,7 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets [Cmdlet(VerbsCommon.Get, "MgContext", DefaultParameterSetName = Constants.UserParameterSet)] [OutputType(typeof(IAuthContext))] - public class GetMGContext: PSCmdlet + public class GetMgContext: PSCmdlet { protected override void BeginProcessing() { diff --git a/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs new file mode 100644 index 0000000000..931b430b59 --- /dev/null +++ b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets +{ + using Microsoft.Graph.PowerShell.Authentication.Extensions; + using Microsoft.Graph.PowerShell.Authentication.Models; + using System; + using System.Linq; + using System.Management.Automation; + using static Microsoft.Graph.PowerShell.Authentication.Common.GraphProfile; + + [Cmdlet(VerbsCommon.Get, "MgProfile")] + [OutputType(typeof(PSGraphServiceProfile))] + public class GetMgProfile: PSCmdlet + { + [Parameter(Mandatory = false)] + [ValidateNotNullOrEmpty] + public string[] ModuleName { get; set; } + + [Parameter(Mandatory = false)] + public SwitchParameter ListAvailable { get; set; } + + protected override void ProcessRecord() + { + base.ProcessRecord(); + try + { + bool isModuleNameBound = this.IsBound(nameof(ModuleName)); + bool isListAvailableBound = this.IsBound(nameof(ListAvailable)); + string[] moduleNames = isModuleNameBound ? ModuleName : new string[] { }; + string[] profiles = isModuleNameBound || isListAvailableBound + ? GetProfiles(InvokeCommand, isListAvailableBound, moduleNames) + : new string[] { + // TODO: Get profile from session object. + }; + if (profiles.Any((p) => !string.IsNullOrWhiteSpace(p))) + { + WriteObject(profiles.Where((profile) => !string.IsNullOrWhiteSpace(profile)) + .Select((p) => PSGraphServiceProfile.Create(p)), true); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, string.Empty, ErrorCategory.CloseError, null)); + } + } + } +} diff --git a/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs b/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs new file mode 100644 index 0000000000..4828e75863 --- /dev/null +++ b/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets +{ + using Microsoft.Graph.PowerShell.Authentication.Extensions; + using System; + using System.Linq; + using System.Management.Automation; + using static Microsoft.Graph.PowerShell.Authentication.Common.GraphProfile; + + /// + /// Select the current Microsoft Graph profile. + /// + [Cmdlet(VerbsCommon.Select, "MgProfile", SupportsShouldProcess = true)] + [OutputType(typeof(bool))] + public class SelectMgProfile: PSCmdlet + { + [Parameter(Mandatory = true)] + [Alias("ProfileName")] + [ValidateNotNullOrEmpty] + public string Name { get; set; } + + [Parameter] + public SwitchParameter PassThru { get; set; } + + protected override void ProcessRecord() + { + base.ProcessRecord(); + try + { + if (this.IsParameterBound(c => c.Name)) + { + PSModuleInfo[] modules = GetModules(InvokeCommand).Where(m => GetProfiles(m).Contains(Name)).ToArray(); + string moduleList = string.Join(", ", modules.Select(m => m.Name)); + if (ShouldProcess($"Modules ${moduleList}", $"Load modules with profile ${Name}")) + { + // TODO: Set selected profile to the GraphSession object. + // TODO: Consider persisting the GraphSession or settings on disk. + ReloadModules(InvokeCommand, modules); + if (PassThru.IsPresent && PassThru.ToBool()) + { + WriteObject(true); + } + } + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, string.Empty, ErrorCategory.CloseError, null)); + } + } + } +} diff --git a/src/Authentication/Authentication/Common/GraphProfile.cs b/src/Authentication/Authentication/Common/GraphProfile.cs new file mode 100644 index 0000000000..98b13f9684 --- /dev/null +++ b/src/Authentication/Authentication/Common/GraphProfile.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Common +{ + using System; + using System.Management.Automation; + using System.Linq; + using System.Collections; + using System.Collections.ObjectModel; + using System.IO; + + /// + /// Methods for working with Microsoft Graph profiles. + /// + internal static class GraphProfile + { + public static string[] GetProfiles(CommandInvocationIntrinsics invokeCommand, bool listAvailable = false, params string[] moduleNames) + { + return GetModules(invokeCommand, listAvailable, moduleNames).SelectMany(GetProfiles).Distinct().ToArray(); + } + + public static PSModuleInfo[] GetModules(CommandInvocationIntrinsics invokeCommand, bool listAvailable = false, params string[] moduleNames) + { + string nameParameter = moduleNames != null && moduleNames.Any() ? $" -Name {GetCommaSeparatedQuotedList(moduleNames)}" : String.Empty; + string listAvailableParameter = listAvailable ? " -ListAvailable" : String.Empty; + string command = $"Get-Module{nameParameter}{listAvailableParameter}"; + Collection modules = listAvailable ? PowerShell.Create().AddScript(command).Invoke() : invokeCommand.NewScriptBlock(command).Invoke(); + return modules != null ? modules.Select(m => m?.BaseObject as PSModuleInfo).Where(m => m != null).ToArray() : new PSModuleInfo[] { }; + + } + + public static string[] GetProfiles(PSModuleInfo moduleInfo) + { + var moduleProfileInfo = ((moduleInfo?.PrivateData as Hashtable)?["PSData"] as Hashtable)?["Profiles"]; + var moduleProfiles = moduleProfileInfo as object[] ?? (moduleProfileInfo != null ? new[] { moduleProfileInfo } : null); + return moduleProfiles != null && moduleProfiles.Any() ? moduleProfiles.Cast().ToArray() : new string[] { }; + } + + public static void ReloadModules(CommandInvocationIntrinsics invokeCommand, params PSModuleInfo[] moduleInfos) + { + var modulePaths = GetCommaSeparatedQuotedList(moduleInfos.Select(GetModulePath).ToArray()); + if (!String.IsNullOrEmpty(modulePaths)) + { + var command = $"Import-Module -Name {modulePaths} -Force"; + invokeCommand.NewScriptBlock(command).Invoke(); + } + } + + private static string GetCommaSeparatedQuotedList(params string[] items) + { + return string.Join(", ", items.Where(i => !string.IsNullOrEmpty(i)).Select(i => $"'{i}'")); + } + + private static string GetModulePath(PSModuleInfo moduleInfo) + { + var scriptPsd1 = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1"); + return moduleInfo.ModuleType == ModuleType.Script && File.Exists(scriptPsd1) ? scriptPsd1 : moduleInfo.Path; + } + } +} diff --git a/src/Authentication/Authentication/Constants.cs b/src/Authentication/Authentication/Constants.cs index 0c9a8a5ebd..64197d0067 100644 --- a/src/Authentication/Authentication/Constants.cs +++ b/src/Authentication/Authentication/Constants.cs @@ -16,5 +16,6 @@ public static class Constants internal const string UserCacheFileName = "userTokenCache.bin3"; internal const string AppCacheFileName = "appTokenCache.bin3"; internal static readonly string TokenCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".graph"); + internal const string ProfileDescription = "A snapshot of the Microsoft Graph {0} API for {1} cloud."; } } diff --git a/src/Authentication/Authentication/Extensions/IEnumerableExtensions.cs b/src/Authentication/Authentication/Extensions/IEnumerableExtensions.cs new file mode 100644 index 0000000000..7c1f8eb5bb --- /dev/null +++ b/src/Authentication/Authentication/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Extensions +{ + using System; + using System.Collections.Generic; + internal static class IEnumerableExtensions + { + /// + /// Perform an action on each element of a sequence. + /// + /// Type of elements in the sequence. + /// The sequence. + /// The action to perform. + public static void ForEach(this IEnumerable sequence, Action action) + { + foreach (T element in sequence) + { + action(element); + } + } + } +} diff --git a/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs b/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs new file mode 100644 index 0000000000..6ddf2f5d47 --- /dev/null +++ b/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Extensions +{ + using Microsoft.Graph.PowerShell.Authentication.Helpers; + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq.Expressions; + using System.Management.Automation; + internal static class PSCmdletExtensions + { + /// + /// Executes a PowerShell script. + /// + /// The output type to return. + /// The executing cmdlet. + /// The PowerShell scrip to execute. + /// The result for the executed script. + public static List ExecuteScript(this PSCmdlet cmdlet, string contents) + { + List output = new List(); + + using (PowerShell powershell = PowerShell.Create(RunspaceMode.CurrentRunspace)) + { + powershell.AddScript(contents); + Collection result = powershell.Invoke(); + + if (cmdlet.SessionState != null) + { + powershell.Streams.Error.ForEach(e => cmdlet.WriteError(e)); + powershell.Streams.Verbose.ForEach(r => cmdlet.WriteVerbose(r.Message)); + powershell.Streams.Warning.ForEach(r => cmdlet.WriteWarning(r.Message)); + } + + if (result != null && result.Count > 0) + { + output.AddRange(result); + } + } + + return output; + } + + /// + /// Determines is a parameter has been provided by the user. + /// + /// The executing cmdlet. + /// The name of the parameter to check. + /// True is the parameter was set by the user, otherwise false. + public static bool IsBound(this PSCmdlet cmdlet, string parameterName) + { + return cmdlet.MyInvocation?.BoundParameters.ContainsKey(parameterName) ?? false; + } + + /// + /// Determines is a parameter has been provided by the user. + /// + /// Cmdlet type. + /// Property type. + /// The executing cmdlet. + /// The parameter to check + /// True is the parameter was set by the user, otherwise false. + public static bool IsParameterBound(this TPSCmdlet cmdlet, Expression> propertySelector) where TPSCmdlet : PSCmdlet + { + var propName = ((MemberExpression)propertySelector.Body).Member.Name; + return cmdlet.IsBound(propName); + } + } +} diff --git a/src/Authentication/Authentication/Extensions/StringExtensions.cs b/src/Authentication/Authentication/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..e44053b126 --- /dev/null +++ b/src/Authentication/Authentication/Extensions/StringExtensions.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Extensions +{ + using System; + public static class StringExtensions + { + /// + /// Indicates whether a specified string is null, empty, consists only of white-space, or has the specified search value. + /// + /// The target string to look in. + /// The substring to seek. + /// The to use. This defaults to . + /// true if the searchValue parameter occurs within this string; otherwise false. + public static bool ContainsNotNull(this string target, string searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase) + { + if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(searchValue)) + { + return false; + } + + switch (comparison) + { + case StringComparison.CurrentCultureIgnoreCase: + case StringComparison.OrdinalIgnoreCase: + target = target.ToLower(); + searchValue = searchValue.ToLower(); + break; + case StringComparison.InvariantCultureIgnoreCase: + target = target.ToLowerInvariant(); + searchValue = searchValue.ToLowerInvariant(); + break; + } + + return target.Contains(searchValue); + } + } +} diff --git a/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs b/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs new file mode 100644 index 0000000000..d4a407da58 --- /dev/null +++ b/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Models +{ + using Microsoft.Graph.PowerShell.Authentication.Extensions; + using System; + using System.Globalization; + + /// + /// Microsoft Graph service profile. + /// + public class PSGraphServiceProfile + { + /// + /// The name of the profile. + /// + public string Name { get; set; } + + /// + /// The description of a profile. + /// + public string Description { get; set; } + + /// + /// Creates a new object. + /// + /// The name of the profile to create. + /// A new . + internal static PSGraphServiceProfile Create(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + return new PSGraphServiceProfile { Name = name, Description = GetProfileDescription(name) }; + } + + /// + /// Gets a full description of the profile. + /// + /// The name of a profile. + /// A full description of a profile. + internal static string GetProfileDescription(string profileName) + { + if (profileName.ContainsNotNull("USGOV-DOD")) + { + return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "the US Government L5 (DOD)"); + } + else if (profileName.ContainsNotNull("USGOV")) + { + return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "the US Government L4"); + } + else if (profileName.ContainsNotNull("Germany")) + { + return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "Germany"); + } + else if (profileName.ContainsNotNull("China")) + { + return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "China"); + } + else + { + return string.Format(CultureInfo.CurrentCulture,Constants.ProfileDescription, GetProfileVersion(profileName), "the Global"); + } + + } + + /// + /// Gets the version of a profile. + /// + /// The name of a profile. + /// The version of a profile. + internal static string GetProfileVersion(string profileName) + { + return profileName.ContainsNotNull("beta") ? "beta" : "v1.0"; + } + } +} From 4b671ac68c3a81974cac7013780772064bb4df8c Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Wed, 3 Jun 2020 10:21:36 -0700 Subject: [PATCH 5/8] Add supported profiles to module manifest. --- .../Authentication/Cmdlets/ConnectGraph.cs | 4 +++- .../Authentication/Cmdlets/GetMgProfile.cs | 4 +--- .../Authentication/Cmdlets/SelectMgProfile.cs | 5 ++--- .../Authentication/Common/GraphProfile.cs | 5 ++--- .../Authentication/Common/GraphSession.cs | 9 +++++++++ tools/Custom/Module.cs | 3 +++ tools/Custom/load-dependency.ps1 | 9 --------- tools/GenerateModules.ps1 | 20 ++++++++++++++++++- 8 files changed, 39 insertions(+), 20 deletions(-) delete mode 100644 tools/Custom/load-dependency.ps1 diff --git a/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs index 8307a830af..708a52a86e 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs @@ -84,7 +84,7 @@ protected override void ProcessRecord() else clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; - // Incremental scope consent without re-instanciating the auth provider. We will use a static instance. + // Incremental scope consent without re-instantiating the auth provider. We will use a static instance. GraphRequestContext graphRequestContext = new GraphRequestContext(); graphRequestContext.CancellationToken = cancellationToken; graphRequestContext.MiddlewareOptions = new Dictionary @@ -170,6 +170,7 @@ private void ThrowParameterError(string parameterName) /// public void OnImport() { + // TODO: Consider checking for a persisted copy of GraphSession or settings on disk. GraphSessionInitializer.InitializeSession(); } @@ -179,6 +180,7 @@ public void OnImport() /// A object. public void OnRemove(PSModuleInfo psModuleInfo) { + // TODO: Consider persisting the GraphSession or settings on disk. GraphSession.Reset(); } } diff --git a/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs index 931b430b59..475cffb2b5 100644 --- a/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs +++ b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs @@ -32,9 +32,7 @@ protected override void ProcessRecord() string[] moduleNames = isModuleNameBound ? ModuleName : new string[] { }; string[] profiles = isModuleNameBound || isListAvailableBound ? GetProfiles(InvokeCommand, isListAvailableBound, moduleNames) - : new string[] { - // TODO: Get profile from session object. - }; + : new string[] { GraphSession.Instance.SelectedProfile }; if (profiles.Any((p) => !string.IsNullOrWhiteSpace(p))) { WriteObject(profiles.Where((profile) => !string.IsNullOrWhiteSpace(profile)) diff --git a/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs b/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs index 4828e75863..a2cb4fe3ea 100644 --- a/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs +++ b/src/Authentication/Authentication/Cmdlets/SelectMgProfile.cs @@ -34,10 +34,9 @@ protected override void ProcessRecord() { PSModuleInfo[] modules = GetModules(InvokeCommand).Where(m => GetProfiles(m).Contains(Name)).ToArray(); string moduleList = string.Join(", ", modules.Select(m => m.Name)); - if (ShouldProcess($"Modules ${moduleList}", $"Load modules with profile ${Name}")) + if (ShouldProcess($"Modules {moduleList}", $"Load modules with profile {Name}")) { - // TODO: Set selected profile to the GraphSession object. - // TODO: Consider persisting the GraphSession or settings on disk. + GraphSession.Instance.SelectedProfile = Name; ReloadModules(InvokeCommand, modules); if (PassThru.IsPresent && PassThru.ToBool()) { diff --git a/src/Authentication/Authentication/Common/GraphProfile.cs b/src/Authentication/Authentication/Common/GraphProfile.cs index 98b13f9684..8858909c1b 100644 --- a/src/Authentication/Authentication/Common/GraphProfile.cs +++ b/src/Authentication/Authentication/Common/GraphProfile.cs @@ -23,17 +23,16 @@ public static string[] GetProfiles(CommandInvocationIntrinsics invokeCommand, bo public static PSModuleInfo[] GetModules(CommandInvocationIntrinsics invokeCommand, bool listAvailable = false, params string[] moduleNames) { - string nameParameter = moduleNames != null && moduleNames.Any() ? $" -Name {GetCommaSeparatedQuotedList(moduleNames)}" : String.Empty; + string nameParameter = $" -Name { (moduleNames != null && moduleNames.Any() ? GetCommaSeparatedQuotedList(moduleNames) : "Microsoft.Graph*" )}"; string listAvailableParameter = listAvailable ? " -ListAvailable" : String.Empty; string command = $"Get-Module{nameParameter}{listAvailableParameter}"; Collection modules = listAvailable ? PowerShell.Create().AddScript(command).Invoke() : invokeCommand.NewScriptBlock(command).Invoke(); return modules != null ? modules.Select(m => m?.BaseObject as PSModuleInfo).Where(m => m != null).ToArray() : new PSModuleInfo[] { }; - } public static string[] GetProfiles(PSModuleInfo moduleInfo) { - var moduleProfileInfo = ((moduleInfo?.PrivateData as Hashtable)?["PSData"] as Hashtable)?["Profiles"]; + var moduleProfileInfo = (moduleInfo?.PrivateData as Hashtable)?["Profiles"]; var moduleProfiles = moduleProfileInfo as object[] ?? (moduleProfileInfo != null ? new[] { moduleProfileInfo } : null); return moduleProfiles != null && moduleProfiles.Any() ? moduleProfiles.Cast().ToArray() : new string[] { }; } diff --git a/src/Authentication/Authentication/Common/GraphSession.cs b/src/Authentication/Authentication/Common/GraphSession.cs index 31fdd65455..26245814b5 100644 --- a/src/Authentication/Authentication/Common/GraphSession.cs +++ b/src/Authentication/Authentication/Common/GraphSession.cs @@ -16,11 +16,20 @@ public class GraphSession : IGraphSession static ReaderWriterLockSlim sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal Guid _graphSessionId; + // Checks if an instance of exists. + public static bool Exists { get { return _initialized; } } + /// /// Gets or Sets . /// public IAuthContext AuthContext { get; set; } + /// + /// The name of the selected Microsoft Graph profile. + /// This defaults to v1.0-beta. + /// + public string SelectedProfile { get; set; } = "v1.0-beta"; + /// /// Gets an instance of . /// diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs index 8806c9623d..1646a82143 100644 --- a/tools/Custom/Module.cs +++ b/tools/Custom/Module.cs @@ -13,6 +13,9 @@ namespace Microsoft.Graph.PowerShell public partial class Module { + /// + /// The selected Microsoft Graph profile. + /// public string ProfileName { get; set; } = "v1.0-beta"; partial void BeforeCreatePipeline(System.Management.Automation.InvocationInfo invocationInfo, ref Runtime.HttpPipeline pipeline) { diff --git a/tools/Custom/load-dependency.ps1 b/tools/Custom/load-dependency.ps1 deleted file mode 100644 index 68d5e3e486..0000000000 --- a/tools/Custom/load-dependency.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -# Ensures 'Graph.Authentication' module is always loaded before this module is loaded. -$ErrorActionPreference = "Stop" -$RequiredModules = "Microsoft.Graph.Authentication" - -$Module = Get-Module $RequiredModules -if ($null -eq $Module) -{ - Import-Module $RequiredModules -Scope Global -} diff --git a/tools/GenerateModules.ps1 b/tools/GenerateModules.ps1 index 1a09e769f4..9555e090a9 100644 --- a/tools/GenerateModules.ps1 +++ b/tools/GenerateModules.ps1 @@ -56,7 +56,7 @@ if (-not (Test-Path $ModuleMappingConfigPath)) { # https://stackoverflow.com/questions/46216038/how-do-i-define-requiredmodules-in-a-powershell-module-manifest-psd1. $ExistingAuthModule = Find-Module "Microsoft.Graph.Authentication" Install-Module $ExistingAuthModule.Name -Repository $RepositoryName -AllowPrerelease -Force -$RequiredGraphModules += @{ ModuleName = $ExistingAuthModule.Name ; RequiredVersion = $ExistingAuthModule.Version } +$RequiredGraphModules += @{ ModuleName = $ExistingAuthModule.Name ; ModuleVersion = $ExistingAuthModule.Version } if ($UpdateAutoRest) { # Update AutoRest. & autorest --reset @@ -129,6 +129,24 @@ $ModuleMapping.Keys | ForEach-Object { & $BuildModulePS1 -Module $ModuleName -ModulePrefix $ModulePrefix -GraphVersion $GraphVersion -ModuleVersion $ModuleVersion -ModulePreviewNumber $ModulePreviewNumber -RequiredModules $RequiredGraphModules -ReleaseNotes $ModuleReleaseNotes } + # Get profiles for generated modules. + $ModuleExportsPath = Join-Path $ModuleProjectDir "\exports" + $Profiles = Get-ChildItem -Path $ModuleExportsPath -Directory | %{ $_.Name} + + # Update module manifest wiht profiles. + $ModuleManifestPath = Join-Path $ModuleProjectDir "$ModulePrefix.$ModuleName.psd1" + [HashTable]$PrivateData = @{ Profiles = $Profiles } + Update-ModuleManifest -Path $ModuleManifestPath -PrivateData $PrivateData + + # Update module psm1 with Graph session profile name. + $ModulePsm1 = Join-Path $ModuleProjectDir "/$ModulePrefix.$ModuleName.psm1" + (Get-Content -Path $ModulePsm1) | ForEach-Object{ + $_ + if ($_ -match '\$instance = \[Microsoft.Graph.PowerShell.Module\]::Instance') { + ' $instance.ProfileName = [Microsoft.Graph.PowerShell.Authentication.GraphSession]::Instance.SelectedProfile' + } + } | Set-Content $ModulePsm1 + if ($LASTEXITCODE) { Write-Error "Failed to build '$ModuleName' module." } From d4b511f6d3f7b67a2cfbaeaad269b72dc97793e4 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Wed, 10 Jun 2020 09:20:01 -0700 Subject: [PATCH 6/8] Export SelectMgProfile & GetMgProfile cmdlet. --- .../Authentication/Microsoft.Graph.Authentication.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 index b8096970d7..1ba38695c6 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 @@ -72,7 +72,7 @@ FormatsToProcess = './Microsoft.Graph.Authentication.format.ps1xml' FunctionsToExport = @() # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = 'Connect-Graph', 'Disconnect-Graph', 'Get-MgContext' +CmdletsToExport = 'Connect-Graph', 'Disconnect-Graph', 'Get-MgContext', 'Get-MgProfile', 'Select-MgProfile' # Variables to export from this module # VariablesToExport = @() From 6a17330dcf6d3607d98b08a80182d0e98018addc Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Thu, 11 Jun 2020 11:44:46 -0700 Subject: [PATCH 7/8] Remove TODOs --- src/Authentication/Authentication/Cmdlets/ConnectGraph.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs index 708a52a86e..6f6c02875e 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectGraph.cs @@ -170,7 +170,6 @@ private void ThrowParameterError(string parameterName) /// public void OnImport() { - // TODO: Consider checking for a persisted copy of GraphSession or settings on disk. GraphSessionInitializer.InitializeSession(); } @@ -180,7 +179,6 @@ public void OnImport() /// A object. public void OnRemove(PSModuleInfo psModuleInfo) { - // TODO: Consider persisting the GraphSession or settings on disk. GraphSession.Reset(); } } From bb1af5d76f8a407b0e6fb61ab3c2bc8895ffbd43 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Wed, 17 Jun 2020 10:03:19 -0700 Subject: [PATCH 8/8] Address feedback. --- .../Authentication/Cmdlets/GetMgProfile.cs | 4 ++-- .../Authentication/Common/GraphProfile.cs | 4 ++-- .../Authentication/Common/GraphSession.cs | 2 +- src/Authentication/Authentication/Constants.cs | 1 + .../Authentication/Extensions/PSCmdletExtensions.cs | 6 +++--- .../Authentication/Extensions/StringExtensions.cs | 4 ++-- .../Authentication/Microsoft.Graph.Authentication.psd1 | 2 +- .../Authentication/Models/PSGraphServiceProfile.cs | 10 +++++----- tools/Templates/readme.md | 2 +- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs index 475cffb2b5..2c2cdb5450 100644 --- a/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs +++ b/src/Authentication/Authentication/Cmdlets/GetMgProfile.cs @@ -27,8 +27,8 @@ protected override void ProcessRecord() base.ProcessRecord(); try { - bool isModuleNameBound = this.IsBound(nameof(ModuleName)); - bool isListAvailableBound = this.IsBound(nameof(ListAvailable)); + bool isModuleNameBound = this.IsParameterBound(nameof(ModuleName)); + bool isListAvailableBound = this.IsParameterBound(nameof(ListAvailable)); string[] moduleNames = isModuleNameBound ? ModuleName : new string[] { }; string[] profiles = isModuleNameBound || isListAvailableBound ? GetProfiles(InvokeCommand, isListAvailableBound, moduleNames) diff --git a/src/Authentication/Authentication/Common/GraphProfile.cs b/src/Authentication/Authentication/Common/GraphProfile.cs index 8858909c1b..0f9c028a05 100644 --- a/src/Authentication/Authentication/Common/GraphProfile.cs +++ b/src/Authentication/Authentication/Common/GraphProfile.cs @@ -23,9 +23,9 @@ public static string[] GetProfiles(CommandInvocationIntrinsics invokeCommand, bo public static PSModuleInfo[] GetModules(CommandInvocationIntrinsics invokeCommand, bool listAvailable = false, params string[] moduleNames) { - string nameParameter = $" -Name { (moduleNames != null && moduleNames.Any() ? GetCommaSeparatedQuotedList(moduleNames) : "Microsoft.Graph*" )}"; + string nameParameter = $"-Name { (moduleNames != null && moduleNames.Any() ? GetCommaSeparatedQuotedList(moduleNames) : "Microsoft.Graph*" )}"; string listAvailableParameter = listAvailable ? " -ListAvailable" : String.Empty; - string command = $"Get-Module{nameParameter}{listAvailableParameter}"; + string command = $"Get-Module {nameParameter}{listAvailableParameter}"; Collection modules = listAvailable ? PowerShell.Create().AddScript(command).Invoke() : invokeCommand.NewScriptBlock(command).Invoke(); return modules != null ? modules.Select(m => m?.BaseObject as PSModuleInfo).Where(m => m != null).ToArray() : new PSModuleInfo[] { }; } diff --git a/src/Authentication/Authentication/Common/GraphSession.cs b/src/Authentication/Authentication/Common/GraphSession.cs index 26245814b5..af968e6a39 100644 --- a/src/Authentication/Authentication/Common/GraphSession.cs +++ b/src/Authentication/Authentication/Common/GraphSession.cs @@ -28,7 +28,7 @@ public class GraphSession : IGraphSession /// The name of the selected Microsoft Graph profile. /// This defaults to v1.0-beta. /// - public string SelectedProfile { get; set; } = "v1.0-beta"; + public string SelectedProfile { get; set; } = Constants.DefaultProfile; /// /// Gets an instance of . diff --git a/src/Authentication/Authentication/Constants.cs b/src/Authentication/Authentication/Constants.cs index 72463aa0b2..0d403f581e 100644 --- a/src/Authentication/Authentication/Constants.cs +++ b/src/Authentication/Authentication/Constants.cs @@ -16,5 +16,6 @@ public static class Constants internal static readonly string TokenCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".graph"); internal const string ProfileDescription = "A snapshot of the Microsoft Graph {0} API for {1} cloud."; internal const string TokenCacheServiceName = "com.microsoft.graph.powershell.sdkcache"; + internal const string DefaultProfile = "v1.0-beta"; } } diff --git a/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs b/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs index 6ddf2f5d47..2eedc28447 100644 --- a/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs +++ b/src/Authentication/Authentication/Extensions/PSCmdletExtensions.cs @@ -17,7 +17,7 @@ internal static class PSCmdletExtensions /// /// The output type to return. /// The executing cmdlet. - /// The PowerShell scrip to execute. + /// The PowerShell script to execute. /// The result for the executed script. public static List ExecuteScript(this PSCmdlet cmdlet, string contents) { @@ -50,7 +50,7 @@ public static List ExecuteScript(this PSCmdlet cmdlet, string contents) /// The executing cmdlet. /// The name of the parameter to check. /// True is the parameter was set by the user, otherwise false. - public static bool IsBound(this PSCmdlet cmdlet, string parameterName) + public static bool IsParameterBound(this PSCmdlet cmdlet, string parameterName) { return cmdlet.MyInvocation?.BoundParameters.ContainsKey(parameterName) ?? false; } @@ -66,7 +66,7 @@ public static bool IsBound(this PSCmdlet cmdlet, string parameterName) public static bool IsParameterBound(this TPSCmdlet cmdlet, Expression> propertySelector) where TPSCmdlet : PSCmdlet { var propName = ((MemberExpression)propertySelector.Body).Member.Name; - return cmdlet.IsBound(propName); + return cmdlet.IsParameterBound(propName); } } } diff --git a/src/Authentication/Authentication/Extensions/StringExtensions.cs b/src/Authentication/Authentication/Extensions/StringExtensions.cs index e44053b126..d3474d4d3e 100644 --- a/src/Authentication/Authentication/Extensions/StringExtensions.cs +++ b/src/Authentication/Authentication/Extensions/StringExtensions.cs @@ -13,8 +13,8 @@ public static class StringExtensions /// The target string to look in. /// The substring to seek. /// The to use. This defaults to . - /// true if the searchValue parameter occurs within this string; otherwise false. - public static bool ContainsNotNull(this string target, string searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase) + /// true if the searchValue parameter occurs within this string; otherwise, false. + public static bool ContainsValue(this string target, string searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase) { if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(searchValue)) { diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 index 1ba38695c6..0eef99381d 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 @@ -12,7 +12,7 @@ RootModule = './Microsoft.Graph.Authentication.psm1' # Version number of this module. -ModuleVersion = '0.7.0' +ModuleVersion = '0.9.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' diff --git a/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs b/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs index d4a407da58..12f43598c5 100644 --- a/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs +++ b/src/Authentication/Authentication/Models/PSGraphServiceProfile.cs @@ -44,19 +44,19 @@ internal static PSGraphServiceProfile Create(string name) /// A full description of a profile. internal static string GetProfileDescription(string profileName) { - if (profileName.ContainsNotNull("USGOV-DOD")) + if (profileName.ContainsValue("USGOV-DOD")) { return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "the US Government L5 (DOD)"); } - else if (profileName.ContainsNotNull("USGOV")) + else if (profileName.ContainsValue("USGOV")) { return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "the US Government L4"); } - else if (profileName.ContainsNotNull("Germany")) + else if (profileName.ContainsValue("Germany")) { return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "Germany"); } - else if (profileName.ContainsNotNull("China")) + else if (profileName.ContainsValue("China")) { return string.Format(CultureInfo.CurrentCulture, Constants.ProfileDescription, GetProfileVersion(profileName), "China"); } @@ -74,7 +74,7 @@ internal static string GetProfileDescription(string profileName) /// The version of a profile. internal static string GetProfileVersion(string profileName) { - return profileName.ContainsNotNull("beta") ? "beta" : "v1.0"; + return profileName.ContainsValue("beta") ? "beta" : "v1.0"; } } } diff --git a/tools/Templates/readme.md b/tools/Templates/readme.md index 8887d105a1..52ead7e470 100644 --- a/tools/Templates/readme.md +++ b/tools/Templates/readme.md @@ -14,6 +14,6 @@ subject-prefix: '' ### Versioning ``` yaml -module-version: 0.5.2 +module-version: 0.9.0 release-notes: See https://aka.ms/GraphPowerShell-Release. ```