src/cmdlets/Test-GraphSettings.ps1
# Copyright 2021, Adam Edwards # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. . (import-script ../client/LocalProfile) <# .SYNOPSIS Validates serialized or deserialized settings and when valid returns the resulting profiles and deserialization. .DESCRIPTION Test-GraphSettings processes an AutoGraph settings file in either serialized or deserialized form to determine whether it constitutes well-formed settings input. This processing is the same as that performed by AutoGraph at module load time, so if the command is successful, AutoGraph will also be successful when loading the settings, and if it's not successful, AutoGraph will also fail to load them. Thus the command is useful when making changes to a settings file or creating new settings, as the settings contents can be validated before inducing a runtime failure when the module is loaded. If the input settings data are not valid, the command fails. .PARAMETER Path By default, Test-GraphSettings validates the file at the default location, ~/.autographps/settings.json. If the Path parameter is specified, then Test-GraphSettings validates the file present at the location specified by Path. .PARAMETER NonStrict By default, Test-GraphSettings fails even in cases where AutoGraph would emit a warning rather than fail an operation when processing settings at module load time or when as settings refresh is implicitly or explicitly invoked. The warnings mean that some configuration was skipped, but other valid settings are still applied. To make Test-GraphSettings treat such errors as non-fatal in the same way, specify the NonStrict parameter. Note that errors such as JSON parsing errors will still result in a failure even with NonStrict specified, which is also true for the settings validation at module load when a parsing error is encountered. .OUTPUTS If successful, this cmdlet returns an object that includes the following properties: * Path: The location of the file (if any) from which settings were read * DefaultProfileName: The name of the default profile (if any) specified in the settings * ProfileNames: The names of all profiles specified in the settings * ConnectionNames: The names of all connections specified in the settings * EndpointNames: The names of all the endpoints specified in the settings * Settings: The deserialized representation of the settings read from the file or from the InputObject parameter * ValidSettings: The deserialized representation of the subset of the settings (and their properties) represented by the Settings output property that were found to be valid by the command. This will only be different from Settings when NonStrict is specified and there is at least one settings error. .EXAMPLE Test-GraphSettings Path: ~/.autographps/settings.json DefaultProfileName : Corp ProfileNames : {Corp, Developer, Production, Personal} ConnectionNames : {Corp, Dev, Prod, PreProd} EndpointNames : {Dev, PreProd} In this example, Test-GraphSettings is invoked with no arguments, so it processes the file at the default settings file location. .EXAMPLE Test-GraphSettings ~/Documents/my-sharedsettings.json Path: ~/Documents/my-sharedsettings.json DefaultProfileName : Company ProfileNames : {Internal, Partner, Production} ConnectionNames : {Company, PartnerConnection, Prod} EndpointNames : {Test, Prod} Test-GraphSettings may also be invoked with an explicit path to a file. This could be useful for building tools that manage settings -- a "temporary" settings file could be validated, and then moved or copied to its permanent destination for instance after it is known to be correct. .EXAMPLE @{connections=@{list=@( @{name='Company';appId='65e8321f-e343-4c89-9fd7-2adb63761b40'} @{name='TestApp';appId='dfedd96b-0118-4f21-bfa3-f4de568868be'})}} | Test-GraphSettings Path: DefaultProfileName : ProfileNames : ConnectionNames : {Company, TestApp} EndpointNames : In this case, instead of specifying the settings using a file path, the InputObject parameter was used to supply objects serializable to the JSON settings schema. The specified settings contained two connections, so the output of the command includes those names using the ConnectionNames property. .EXAMPLE Test-GraphSettings | Select-Object -ExpandProperty Settings defaultProfile connections profiles -------------- ----------- -------- Collaboration @{list=System.Object[]} @{list=System.Object[]} This example shows how the Settings property can be used to obtain the deserialized form of the settings. In this case, the input originated from the default settings file -- if it were reserialized the resulting JSON would be semantically the same as the actual settings file itself. This output could also have been obtained by reading the settings file with Get-Content and sending that output to ConvertFrom-Json. .EXAMPLE '{"profiles":{"list":[{"promptColor":"magenta","name":"Work"}]},"connections":{"list":[{"appId":"This should be a guid","name":"Production"},{"appId":"895ab43e-e40f-43da-9a3b-44face21437f","name":"MyWork"}]}}' | >> Test-GraphSettings -NonStrict WARNING: Property 'appId' of setting 'Admin' of type 'connections' is invalid: Specified value 'This should be a guid' is not a valid guid WARNING: Setting 'Admin' of type 'connections' will be ignored due to an invalid value for required property 'appId' Path: DefaultProfileName : ProfileNames : Work ConnectionNames : MyWork EndpointNames : The supplied input via the InputObject parameter is actually JSON instead of an object. Here we see how NonStrict allows the command to succeed even when the settings specified as input are invalid. In this the conneection setting "Production" violated the constraint that its appId property must be a valid guid. While the command success, the output omits the "Production" connection because it was not valid, and only emits the one valid connection "MyWork." Additionally, the Warning output stream displays messages to the console with more detail on why the invalid connection was ignored. This behavior mirrors that of the module itself when it loads settings at startup -- invalid settings are ignored and Warning stream messages are emitted to give awareness to the user that they may need to make corrections to the settings to obtain desired functionality. .EXAMPLE $updatedSettings | Test-GraphSettings -NonStrict | Select-Object -ExpandProperty ValidSettings | ConvertTo-Json -Depth 4 | Out-File ~/alternatesettings.json WARNING: Property 'appId' of setting 'Production of type 'connections' is invalid: Specified value 'This should be a guid' is not a valid guid WARNING: Setting 'Production' of type 'connections' will be ignored due to an invalid value for required property 'appId' In this example, settings are specified through the InputObject parameter and the ValidSettings property of the command's output is serialized and written to the file. The structure of the ValidSettings property is the same as that of Settings, except that the settings and properties that it contains are the subset of those from Settings that are "valid," i.e. did not have data type or reference errors. When there are no errors, the Settings and ValidSettings properties are the same. Since the command terminates with an error and no output when any errors are encountered and the NonStrict parameter is not specified, ValidSettings and Settings can only differ when NonStrict is specified. This example shows how to create a new, error-free settings file from settings that contain errors. Note that in this case Warning stream output is still output to the console indicating the errors that were encountered and filtered out of the settings represented by ValidSettings. .EXAMPLE $newSettings | Test-GraphSettings -NonStrict -OutVariable settingsInfo 3>&1 | out-null $settingsInfo.ValidSettings Name Value ---- ----- endpoints {list} defaultProfile connections {list} profiles {list} This example is similar to previous examples that use NonStrict, but in this case the Warning stream output is suppressed by redirecting it to the Output stream. The actual output is sent to the variable named 'settingsInfo' specified by the OutVariable parameter of Test-GraphSettings. The variable is then evaluated and emitted to the console. This demonstrates an approach for validating settings using NonStrict and capturing the results without also emitting to the Warning stream. .LINK Get-GraphProfile #> function Test-GraphSettings { [cmdletbinding(positionalbinding=$false)] param( [parameter(parametersetname='path', position=0)] [string] $Path, [parameter(parametersetname='data', valuefrompipeline=$true, mandatory=$true)] [object] $InputObject, [switch] $NonStrict ) $settingsPath = if ( $Path ) { $Path } elseif ( ! $InputObject ) { $::.LocalProfile |=> GetSettingsFileLocation } $failOnWarnings = ! $NonStrict.IsPresent -or ( $WarningPreference -eq 'Stop' ) $settings = new-so LocalSettings $settingsPath $true $failOnWarnings $settings |=> Load $false $InputObject $defaultProfileName = $null $profileNames = $null $endpointNames = $null $connectionNames = $null $serializableSettings = $null if ( $settings.settingsData ) { # This method returns only the settings and properties that are validated $serializableSettings = $::.LocalProfile |=> GetValidatedSerializableSettings $settings $defaultProfileName = $serializableSettings.defaultProfile $profilenames = if ( $serializableSettings.profiles.list ) { $serializableSettings.profiles.list.name } $connectionNames = if ( $serializableSettings.connections.list ) { $serializableSettings.connections.list.name } $endpointNames = if ( $serializableSettings.endpoints.list ) { $serializableSettings.endpoints.list.name } } elseif ( $Path ) { throw "The specified settings file '$Path' could not be found." } $settingsInfo = [PSCustomObject] @{ Path = $settingsPath Settings = $settings.settingsData ValidSettings = $serializableSettings DefaultProfileName = $defaultProfileName ProfileNames = $profileNames | sort-object ConnectionNames = $connectionNames | sort-object EndpointNames = $endpointNames | sort-object } $settingsInfo.pstypenames.insert(0, 'AutoGraph.SettingsInfo') $settingsInfo } |