SMAAuthoringToolkitInner.psm1
$script:ConfigurationFileName = "SMAAuthoringToolkitConfig.json" $script:ConfigurationPath = "$env:USERPROFILE\$script:ConfigurationFileName" $script:LocalAssetsPath = "$PSScriptRoot\LocalAssets.json" $script:SecureLocalAssetsPath = "$PSScriptRoot\SecureLocalAssets.json" $script:StartProfileSnippetForPowerShellToLoadSMAISEAddOn = "# Start SMAISEAddOn snippet" $script:EndProfileSnippetForPowerShellToLoadSMAISEAddOn = "# End SMAISEAddOn snippet" $script:IseAddonPath = "\SMAISEAddon\Microsoft.SystemCenter.ServiceManagementAutomation.ISEAddOn.dll" $script:PowerShellToLoadSMAIseAddOnGeneric = @" `n # Start SMAISEAddOn snippet Import-Module SMAAuthoringToolkit # End SMAISEAddOn snippet "@ $script:IseProfileFileName = "Microsoft.PowerShellISE_profile.ps1" function _findObjectByName { param( [object] $ObjectArray, [string] $Name ) $ObjectArray | Where-Object { return $_.Name -eq $Name } } function _DecryptValue { param( [object] $Value, [switch] $SupressCouldNotDecryptWarning ) $Configuration = Get-SMAAuthoringToolkitConfiguration if($Value -isnot [string]) { ## the local assets files store all encrypted values as strings, so if value is not a string, it is not an encrypted value. ## in this case, return the raw value Write-Verbose "SMAAuthoringToolkit: Value is not encrypted. Returning raw value without decrypting" return $Value } elseif($Configuration.EncryptionCertificateThumbprint -eq "none") { Write-Verbose "SMAAuthoringToolkit: No encryption certificate specified. Returning raw value without decrypting" return $Value } else { $Thumbprint = $Configuration.EncryptionCertificateThumbprint Write-Verbose "SMAAuthoringToolkit: Decrypting encrypted value '$Value' using encryption certificate with thumbprint '$Thumbprint'" if (Test-Path -Path Cert:\CurrentUser\My\$Thumbprint) { $Cert = Get-Item -Path Cert:\CurrentUser\My\$Thumbprint try { $Encrypted = [Convert]::FromBase64String($Value) $Bytes = $Cert.PrivateKey.Decrypt($Encrypted, $True) $EncryptedValue = [Text.Encoding]::UTF8.GetString($Bytes) # the encrypted value is a JSON string (so that we can encrypt non-string types), so convert it back to a proper object $EncryptedValue = ConvertFrom-Json -InputObject $EncryptedValue return $EncryptedValue } catch { if(!$SupressCouldNotDecryptWarning.IsPresent) { Write-Warning "SMAAuthoringToolkit: Warning - Could not decrypt value '$Value' using encryption certificate with thumbprint '$Thumbprint'. Returning raw value instead. Are you sure the value was encrypted with this certificate?" } return $Value } } else { throw "Encryption certificate with thumbprint '$Thumbprint' is not installed in the user cert store" } } } function _EncryptValue { param( [object] $Value ) $Configuration = Get-SMAAuthoringToolkitConfiguration if($Configuration.EncryptionCertificateThumbprint -eq "none") { Write-Verbose "SMAAuthoringToolkit: No encryption certificate specified. Not encrypting value" return $Value } else { $Thumbprint = $Configuration.EncryptionCertificateThumbprint Write-Verbose "SMAAuthoringToolkit: Encrypting value using encryption certificate with thumbprint '$Thumbprint'" if (Test-Path -Path Cert:\CurrentUser\My\$Thumbprint) { # convert the value to a JSON string so that we don't lost type info when decrypting $JsonValue = ConvertTo-Json -InputObject $Value -Depth 999 $Cert = Get-Item -Path Cert:\CurrentUser\My\$Thumbprint $Bytes = [Text.Encoding]::UTF8.GetBytes($JsonValue) $Encrypted = $Cert.PublicKey.Key.Encrypt($Bytes, $True) $Value = [Convert]::ToBase64String($Encrypted) return $Value } else { throw "Encryption certificate with thumbprint '$Thumbprint' is not installed in the user cert store" } } } <# .SYNOPSIS Copies configuration file for SMAAuthoringToolkit to user's profile directory #> function Copy-ConfigFile { Write-Verbose "SMAAuthoringToolkit: Copying '$script:ConfigurationFileName' configuration file to '$script:ConfigurationPath'" $ConfigurationFile = Get-Item -Path $script:ConfigurationPath -ErrorAction SilentlyContinue if(!$ConfigurationFile) { Copy-Item -Path (Join-Path $PSScriptRoot $script:ConfigurationFileName) -Destination $script:ConfigurationPath } else { Write-Verbose "SMAAuthoringToolkit: '$script:ConfigurationPath' already present. Not copying file." } } <# .SYNOPSIS Sets up the Service Management Automation ISE add-on for use in the PowerShell ISE. #> function Install-SMAIseAddOn { $IseProfilePath = Join-Path (Split-Path $Profile) $script:IseProfileFileName if(!(Test-Path $IseProfilePath)) { Write-Verbose "SMAAuthoringToolkit: '$IseProfilePath' does not exist, creating it" New-Item -Path $IseProfilePath -ItemType File -Force | Out-Null } $ProfileContent = Get-Content $IseProfilePath -Raw if(!$ProfileContent) { $ProfileContent = "" } $StartProfileSnippetIndex = $ProfileContent.IndexOf($script:StartProfileSnippetForPowerShellToLoadSMAISEAddOn) # add content to PS ISE profile to load ISe add on on start up Write-Verbose "SMAAuthoringToolkit: Adding content to '$IseProfilePath' to cause ISE add-on to load on PowerShell ISE start up" if($StartProfileSnippetIndex -eq -1) { $IsRunningInISE = (Split-Path $Profile -Leaf) -eq $script:IseProfileFileName # add loading of the ISE add-on into the PS ISE Profile so it is automatically loaded each time the ISE is opened $IseAddOnModuleFolderPath = Split-Path $PSScriptRoot -Parent $PowerShellToLoadSMAIseAddOnWithPath = $script:PowerShellToLoadSMAIseAddOnGeneric -f $IseAddOnModuleFolderPath, $script:IseAddonPath Add-Content $IseProfilePath $PowerShellToLoadSMAISEAddOnWithPath if($IsRunningInISE) { # load the ISE add-on into the PS ISE session already open Write-Verbose "SMAAuthoringToolkit: Loading ISE add-on into this PowerShell ISE session" Invoke-Expression $PowerShellToLoadSMAIseAddOnWithPath } } else { Write-Verbose "SMAAuthoringToolkit: Content already present. Not adding any content." } } <# .SYNOPSIS Removes the Service Management Automation ISE add-on from the PowerShell ISE. #> function Uninstall-SMAIseAddOn { $IseProfilePath = Join-Path (Split-Path $Profile) $script:IseProfileFileName $ProfileContent = Get-Content $IseProfilePath -Raw $StartProfileSnippetIndex = $ProfileContent.IndexOf($script:StartProfileSnippetForPowerShellToLoadSMAISEAddOn) $EndProfileSnippetIndex = $ProfileContent.IndexOf($script:EndProfileSnippetForPowerShellToLoadSMAISEAddOn) if($StartProfileSnippetIndex -gt -1 -and $EndProfileSnippetIndex -gt -1) { Write-Verbose "SMAAuthoringToolkit: Removing content from '$IseProfilePath' to cause ISE add-on to no longer load on PowerShell ISE start up" $NewProfileContent = $ProfileContent.Substring(0, $StartProfileSnippetIndex) $NewProfileContent += $ProfileContent.Substring($EndProfileSnippetIndex + $script:EndProfileSnippetForPowerShellToLoadSMAISEAddOn.Length) $NewProfileContent | Set-Content $IseProfilePath } } <# .SYNOPSIS Get a local certificate based on its thumbprint, as part of the Service Management Automation Authoring Toolkit. Not meant to be called directly. #> function Get-SMAAuthoringToolkitLocalCertificate { param( [Parameter(Mandatory=$True)] [string] $Name, [Parameter(Mandatory=$True)] [string] $Thumbprint ) Write-Verbose "SMAAuthoringToolkit: Looking for local certificate with thumbprint '$Thumbprint'" try { $Certificate = Get-Item ("Cert:\CurrentUser\My\" + $Thumbprint) -ErrorAction Stop Write-Output $Certificate } catch { Write-Error "SMAAuthoringToolkit: Certificate asset '$Name' referenced certificate with thumbprint '$Thumbprint' but no certificate with that thumbprint exist on the local system." throw $_ } } <# .SYNOPSIS Get local assets defined for the Service Management Automation Authoring Toolkit. Not meant to be called directly. #> function Get-SMAAuthoringToolkitLocalAsset { param( [Parameter(Mandatory=$True)] [ValidateSet('Variable', 'Certificate', 'PSCredential', 'Connection')] [string] $Type, [Parameter(Mandatory=$True)] [string]$Name ) $Configuration = Get-SMAAuthoringToolkitConfiguration if($Configuration.LocalAssetsPath -eq "default") { Write-Verbose "Grabbing local assets from default location '$script:LocalAssetsPath'" } else { $script:LocalAssetsPath = $Configuration.LocalAssetsPath Write-Verbose "Grabbing local assets from user-specified location '$script:LocalAssetsPath'" } if($Configuration.SecureLocalAssetsPath -eq "default") { Write-Verbose "Grabbing secure local assets from default location '$script:SecureLocalAssetsPath'" } else { $script:SecureLocalAssetsPath = $Configuration.SecureLocalAssetsPath Write-Verbose "Grabbing secure local assets from user-specified location '$script:SecureLocalAssetsPath'" } $LocalAssetsError = "SMAAuthoringToolkit: SMAAuthoringToolkit local assets defined in '$script:LocalAssetsPath' is incorrect. Make sure the file exists, and it contains valid JSON." $SecureLocalAssetsError = "SMAAuthoringToolkit: SMAAuthoringToolkit secure local assets defined in '$script:SecureLocalAssetsPath' is incorrect. Make sure the file exists, and it contains valid JSON." Write-Verbose "SMAAuthoringToolkit: Looking for local value for $Type asset '$Name.'" try { $LocalAssets = Get-Content $script:LocalAssetsPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-Error $LocalAssetsError throw $_ } try { $SecureLocalAssets = Get-Content $script:SecureLocalAssetsPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-Error $SecureLocalAssetsError throw $_ } $Asset = _findObjectByName -ObjectArray $LocalAssets.$Type -Name $Name $AssetWasInSecureLocalAssets = $False if($Asset) { Write-Verbose "SMAAuthoringToolkit: Found local value for $Type asset '$Name.'" $AssetWasInSecureLocalAssets = $False } else { $Asset = _findObjectByName -ObjectArray $SecureLocalAssets.$Type -Name $Name if($Asset) { Write-Verbose "SMAAuthoringToolkit: Found secure local value for $Type asset '$Name.'" $AssetWasInSecureLocalAssets = $True } } if($Asset) { if($Type -eq "Certificate") { $AssetValue = Get-SMAAuthoringToolkitLocalCertificate -Name $Name -Thumbprint $Asset.Thumbprint } elseif($Type -eq "Variable") { if($Asset.Value -eq $Null) { Write-Warning "SMAAuthoringToolkit: Warning - Local Variable asset '$Name' has a value of null. If this was not intended, update its value in your local assets. " } if($AssetWasInSecureLocalAssets) { $AssetValue = (_DecryptValue -Value $Asset.Value) } else { $AssetValue = $Asset.Value } } elseif($Type -eq "Connection") { # Convert PSCustomObject to Hashtable $Temp = @{} $Asset.ValueFields.psobject.properties | ForEach-Object { if($_.Value -eq $Null) { Write-Warning ("SMAAuthoringToolkit: Warning - Local Connection asset '$Name' has a null value for field '" + $_.Name + "'. If this was not intended, update this connection field value in your local assets.") } # even though all connection fields may not be encrypted, try to decrypt them all since we don't know which are encrypted and which are not. # if decryption fails (because field was not encrypted), supress the warning that decryption failed, since could be perfectly fine in this case. # when decryption fails, _DecryptValue returns the raw value, which is what we want $Temp."$($_.Name)" = (_DecryptValue -Value $_.Value -SupressCouldNotDecryptWarning) } $AssetValue = $Temp } elseif($Type -eq "PSCredential") { if($Asset.Password -eq $Null) { Write-Warning "SMAAuthoringToolkit: Warning - Local PSCredential asset '$Name' has a password value of null. If this was not intended, update its password value in your local assets. " } $AssetValue = @{ Username = $Asset.Username Password = (_DecryptValue -Value $Asset.Password) } } Write-Output $AssetValue } else { Write-Verbose "SMAAuthoringToolkit: Local value for $Type asset '$Name' not found." Write-Warning "SMAAuthoringToolkit: Warning - Local value for $Type asset '$Name' not found." } } <# .SYNOPSIS Get the configuration for the Service Management Automation Authoring Toolkit. Not meant to be called directly. #> function Get-SMAAuthoringToolkitConfiguration { $ConfigurationError = "SMAAuthoringToolkit: SMAAuthoringToolkit configuration defined in '$script:ConfigurationPath' is incorrect. Make sure the file exists, contains valid JSON, and contains 'LocalAssetsPath', 'SecureLocalAssetsPath', and 'EncryptionCertificateThumbprint' settings." Write-Verbose "SMAAuthoringToolkit: Grabbing SMAAuthoringToolkit configuration from '$script:ConfigurationPath'" try { $ConfigurationTemp = Get-Content $script:ConfigurationPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-Error $ConfigurationError throw $_ } $Configuration = @{} $ConfigurationTemp | ForEach-Object { $Key = $_.Name $Value = $_.Value $Configuration.$Key = $Value } if(!($Configuration.LocalAssetsPath -and $Configuration.SecureLocalAssetsPath -and $Configuration.EncryptionCertificateThumbprint)) { throw $ConfigurationError } Write-Output $Configuration } <# .SYNOPSIS Get a variable asset from Service Management Automation. Part of the Service Management Automation Authoring Toolkit to help author runbooks locally. #> function Get-AutomationVariable { [OutputType([Object])] param( [Parameter(Mandatory=$true)] [string] $Name ) Write-Verbose "SMAAuthoringToolkit: Looking for local variable asset with name '$Name'" $AssetValue = Get-SMAAuthoringToolkitLocalAsset -Type Variable -Name $Name Write-Output $AssetValue } <# .SYNOPSIS Get a connection asset from Service Management Automation. Part of the Service Management Automation Authoring Toolkit to help author runbooks locally. #> function Get-AutomationConnection { [OutputType([Hashtable])] param( [Parameter(Mandatory=$true)] [string] $Name ) Write-Verbose "SMAAuthoringToolkit: Looking for local connection asset with name '$Name'" $AssetValue = Get-SMAAuthoringToolkitLocalAsset -Type Connection -Name $Name Write-Output $AssetValue } <# .SYNOPSIS Set the value of a variable asset in Service Management Automation. Part of the Service Management Automation Authoring Toolkit to help author runbooks locally. #> function Set-AutomationVariable { param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter(Mandatory=$true)] [object] $Value ) Write-Verbose "SMAAuthoringToolkit: Looking for local variable asset with name '$Name'" $LocalAssetValue = Get-SMAAuthoringToolkitLocalAsset -Type Variable -Name $Name if($LocalAssetValue) { $LocalAssets = Get-Content $script:LocalAssetsPath -Raw | ConvertFrom-Json $SecureLocalAssets = Get-Content $script:SecureLocalAssetsPath -Raw | ConvertFrom-Json $LocalAssets.Variable | ForEach-Object { if($_.Name -eq $Name) { $_.Value = $Value $_.LastModified = Get-Date -Format u } } $SecureLocalAssets.Variable | ForEach-Object { if($_.Name -eq $Name) { $_.Value = (_EncryptValue -Value $Value) $_.LastModified = Get-Date -Format u } } Write-Verbose "SMAAuthoringToolkit: Setting value of local variable asset with name '$Name'" Set-Content $script:LocalAssetsPath -Value (ConvertTo-Json -InputObject $LocalAssets -Depth 999) Set-Content $script:SecureLocalAssetsPath -Value (ConvertTo-Json -InputObject $SecureLocalAssets -Depth 999) } else { throw "Variable '$Name' not found for account 'AuthoringToolkit'" } } <# .SYNOPSIS Get a certificate asset from Service Management Automation. Part of the Service Management Automation Authoring Toolkit to help author runbooks locally. #> function Get-AutomationCertificate { [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] param( [Parameter(Mandatory=$true)] [string] $Name ) Write-Verbose "SMAAuthoringToolkit: Looking for local certificate asset with name '$Name'" $AssetValue = Get-SMAAuthoringToolkitLocalAsset -Type Certificate -Name $Name Write-Output $AssetValue } |