Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Latest version available now on GitHub -> https://github.com/realslacker/PSmRemoteNG
#
# Module manifest for module 'PSmRemoteNG'
#
# Generated by: Shannon Graybrook
#
# Generated on: 7/18/2019
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'PSmRemoteNG.psm1'
# Version number of this module.
ModuleVersion = '2019.7.18'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = '53ef1dab-cabf-4a1a-afb3-af155182c9c3'
# Author of this module
Author = 'Shannon Graybrook'
# Company or vendor of this module
CompanyName = 'Brooksworks'
# Copyright statement for this module
Copyright = 'Shannon Graybrook (c) 2019'
# Description of the functionality provided by this module
Description = 'A module to create mRemoteNG connection files from PowerShell.'
# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions 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 functions to export.
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 = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases 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 aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
# find mRemoteNG install location
$mRNGDirectory = 'HKLM:\SOFTWARE\mRemoteNG', 'HKLM:\SOFTWARE\WOW6432Node\mRemoteNG' |
ForEach-Object { Get-ItemProperty -Path $_ -Name InstallDir -ErrorAction SilentlyContinue } |
Select-Object -First 1 -ExpandProperty InstallDir
# load assemblies
try {
[void][System.Reflection.Assembly]::LoadFile( "$mRNGDirectory\mRemoteNG.exe" )
[void][System.Reflection.Assembly]::LoadFile( "$mRNGDirectory\BouncyCastle.Crypto.dll" )
} catch {
throw $Messages.AssemblyLoadError
}
<#
.SYNOPSIS
Import an mRemoteNG root connections node from a confCons.xml file.
.PARAMETER EncryptionKey
The encryption key for the confCons.xml file.
.EXAMPLE
$RootNode = Import-MRNGRootNode -Path .\confCons.xml -EncryptionKey ( Read-Host 'Encryiption Key' -AsSecureString )
#>
function Import-MRNGRootNode {
[OutputType([mRemoteNG.Tree.Root.RootNodeInfo])]
param(
[Parameter(Mandatory)]
[System.IO.FileInfo]
$Path,
[securestring]
$EncryptionKey = ( ConvertTo-SecureString -String 'mR3m' -AsPlainText -Force )
)
# resolve the path
$Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $Path )
# load the XML
$DataProvider = [mRemoteNG.Config.DataProviders.FileDataProvider]::new( $Path )
$XmlString = $DataProvider.Load()
# create a function to return the password
# this is what mRemoteNG expects
[Func[mRemoteNG.Tools.Optional[securestring]]]$Auth = { $EncryptionKey }
# create a deserializer
$Deserializer = [mRemoteNG.Config.Serializers.Xml.XmlConnectionsDeserializer]::new($Auth)
# return the connections
( $Deserializer.Deserialize( $XmlString ) ).RootNodes.Item(0)
}
<#
.SYNOPSIS
Create an empty mRemoteNG root connections node.
.PARAMETER EncryptionKey
The encryption key to use for this connections file.
.EXAMPLE
$RootNode = New-MRNGRootNode
#>
function New-MRNGRootNode {
[OutputType([mRemoteNG.Tree.Root.RootNodeInfo])]
param(
[securestring]
$EncryptionKey = ( ConvertTo-SecureString -String 'mR3m' -AsPlainText -Force )
)
# convert the encryption key to plaintext
$EncryptionKeyBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $EncryptionKey )
$EncryptionKeyText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( $EncryptionKeyBSTR )
$RootNode = [mRemoteNG.Tree.Root.RootNodeInfo]::new('Connection')
# set a password?
if ( $EncryptionKeyText -ne $RootNode.DefaultPassword ) {
$RootNode.Password = $true
$RootNode.PasswordString = $EncryptionKeyText
}
$RootNode
}
<#
.SYNOPSIS
Create an mRemoteNG container node.
.EXAMPLE
New-MRNGContainer -Name 'Test Container' -Parent $RootNode -Protocol SSH2
.EXAMPLE
$Container = New-MRNGContainer -Name 'Test Container'
$RootContainer.AddChild( $Container )
$Container2 = New-MRNGContainer -Name 'Child Container'
$Container.AddChild( $Container2 )
#>
function New-MRNGContainer {
[OutputType([mRemoteNG.Container.ContainerInfo])]
[CmdletBinding()]
param()
dynamicparam {
$DPDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$SkipParameters = 'Inheritance', 'ConstantID', 'IsContainer'
[mRemoteNG.Container.ContainerInfo].GetProperties() |
Where-Object { $SkipParameters -notcontains $_.Name } |
ForEach-Object {
$Attribute = New-Object System.Management.Automation.ParameterAttribute
$Attribute.ParameterSetName = '__AllParameterSets'
$Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$Collection.Add($Attribute)
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter( $_.Name, $_.PropertyType, $Collection )
$DPDictionary.Add( $_.Name, $Parameter )
}
$Attribute = New-Object System.Management.Automation.ParameterAttribute
$Attribute.ParameterSetName = '__AllParameterSets'
$Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$Collection.Add($Attribute)
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter( 'Inheritance', [hashtable], $Collection )
$DPDictionary.Add( 'Inheritance', $Parameter )
return $DPDictionary
}
process {
$ContainerInfo = [mRemoteNG.Container.ContainerInfo]::new()
# set default port
if ( $PSBoundParameters.Keys -contains 'Protocol' -and $PSBoundParameters.Keys -notcontains 'Port' ) {
$PSBoundParameters.Port = switch ( $PSBoundParameters.Protocol ) {
'RDP' { 3389 }
'VNC' { 5900 }
'SSH1' { 22 }
'SSH2' { 22 }
'Telnet' { 23 }
'Rlogin' { 513 }
'RAW' { 23 }
'HTTP' { 80 }
'HTTPS' { 443 }
'IntApp' { 0 }
}
}
# set connection properties
$PSBoundParameters.Keys |
Where-Object { $_ -notmatch 'Parent|Children|Inheritance' } |
Where-Object { [mRemoteNG.Container.ContainerInfo].GetProperties().Name -contains $_ } |
ForEach-Object { $ContainerInfo.$_ = $PSBoundParameters.$_ }
# configure inheritance
if ( $PSBoundParameters.Keys -contains 'Inheritance' ) {
# process EverythingInherited first
if ( $PSBoundParameters.Inheritance.Keys -contains 'EverythingInherited' ) {
$ContainerInfo.Inheritance.EverythingInherited = $true
}
# process all other inheritence
$PSBoundParameters.Inheritance.Keys |
Where-Object { $_ -ne 'EverythingInherited' } |
ForEach-Object {
$ContainerInfo.Inheritance.$_ = $PSBoundParameters.Inheritance.$_
}
}
# if children is specified we append
if ( $PSBoundParameters.Keys -contains 'Children' ) {
$PSBoundParameters.Children |
ForEach-Object {
$ContainerInfo.AddChild($_)
Write-Verbose ( 'Child ''{0}'' added to ''{1}''.' -f $_.Name, $ContainerInfo.Name )
}
}
# if parent is specified we append, otherwise we output
if ( $PSBoundParameters.Keys -contains 'Parent' ) {
$PSBoundParameters.Parent.AddChild( $ContainerInfo )
Write-Verbose ( 'Container ''{0}'' added to ''{1}''.' -f $ContainerInfo.Name, $PSBoundParameters.Parent.Name )
}
$ContainerInfo
}
}
<#
.SYNOPSIS
Create an mRemoteNG connection node.
.EXAMPLE
New-MRNGConnection -Name 'Test Connection' -HostName '127.0.0.1' -Parent $RootNode -Protocol SSH2 -Inheritance (New-MRNGInheritanceConfiguration -EverythingInherited -Protocol:$false)
.EXAMPLE
$Connection = New-MRNGConnection -Name 'Test Connection' -HostName '127.0.0.1'
$RootContainer.AddChild( $Connection )
#>
function New-MRNGConnection {
[OutputType([mRemoteNG.Connection.ConnectionInfo])]
[CmdletBinding()]
param()
dynamicparam {
$DPDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$SkipParameters = 'Inheritance', 'ConstantID', 'IsContainer'
[mRemoteNG.Connection.ConnectionInfo].GetProperties() |
Where-Object { $SkipParameters -notcontains $_.Name } |
ForEach-Object {
$Attribute = New-Object System.Management.Automation.ParameterAttribute
$Attribute.ParameterSetName = '__AllParameterSets'
$Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$Collection.Add($Attribute)
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter( $_.Name, $_.PropertyType, $Collection )
$DPDictionary.Add( $_.Name, $Parameter )
}
$Attribute = New-Object System.Management.Automation.ParameterAttribute
$Attribute.ParameterSetName = '__AllParameterSets'
$Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$Collection.Add($Attribute)
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter( 'Inheritance', [hashtable], $Collection )
$DPDictionary.Add( 'Inheritance', $Parameter )
return $DPDictionary
}
process {
$ConnectionInfo = [mRemoteNG.Connection.ConnectionInfo]::new()
# set default port
if ( $PSBoundParameters.Keys -contains 'Protocol' -and $PSBoundParameters.Keys -notcontains 'Port' ) {
$PSBoundParameters.Port = switch ( $PSBoundParameters.Protocol ) {
'RDP' { 3389 }
'VNC' { 5900 }
'SSH1' { 22 }
'SSH2' { 22 }
'Telnet' { 23 }
'Rlogin' { 513 }
'RAW' { 23 }
'HTTP' { 80 }
'HTTPS' { 443 }
'IntApp' { 0 }
}
}
# set connection properties
$PSBoundParameters.Keys |
Where-Object { $_ -notmatch 'Parent|Inheritance' } |
Where-Object { [mRemoteNG.Connection.ConnectionInfo].GetProperties().Name -contains $_ } |
ForEach-Object { $ConnectionInfo.$_ = $PSBoundParameters.$_ }
# configure inheritance
if ( $PSBoundParameters.Keys -contains 'Inheritance' ) {
# process EverythingInherited first
if ( $PSBoundParameters.Inheritance.Keys -contains 'EverythingInherited' ) {
$ConnectionInfo.Inheritance.EverythingInherited = $true
}
# process all other inheritence
$PSBoundParameters.Inheritance.Keys |
Where-Object { $_ -ne 'EverythingInherited' } |
ForEach-Object {
$ConnectionInfo.Inheritance.$_ = $PSBoundParameters.Inheritance.$_
}
}
# if parent is specified we append, otherwise we output
if ( $PSBoundParameters.Keys -contains 'Parent' ) {
$PSBoundParameters.Parent.AddChild( $ConnectionInfo )
Write-Verbose ( 'Connection ''{0}'' added to ''{1}''.' -f $ConnectionInfo.Name, $PSBoundParameters.Parent.Name )
}
$ConnectionInfo
}
}
<#
.SYNOPSIS
Helper function to build an inheritance configuration.
.EXAMPLE
New-MRNGInheritanceConfiguration -EverythingInherited -Icon:$false
# inherits everything except the icon
#>
function New-MRNGInheritanceConfiguration {
[OutputType([hashtable])]
[CmdletBinding()]
param()
dynamicparam {
$DPDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
[mRemoteNG.Connection.ConnectionInfoInheritance].GetProperties() |
Where-Object { $_.Name -ne 'Parent' } |
ForEach-Object {
$Attribute = New-Object System.Management.Automation.ParameterAttribute
$Attribute.ParameterSetName = '__AllParameterSets'
$Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$Collection.Add($Attribute)
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter( $_.Name, [switch], $Collection )
$DPDictionary.Add( $_.Name, $Parameter )
}
return $DPDictionary
}
process {
$ReturnHashtable = @{}
$PSBoundParameters.Keys |
Where-Object { [mRemoteNG.Connection.ConnectionInfoInheritance].GetProperties().Name -contains $_ } |
ForEach-Object { $ReturnHashtable.$_ = $PSBoundParameters.$_ }
$ReturnHashtable
}
}
<#
.SYNOPSIS
Exports the mRemoteNG connection file with the encryption parameters specified.
.PARAMETER RootNode
The mRemoteNG root connections node generated by New-MRNGRootNode.
.PARAMETER Path
The path to export the connection file to.
.PARAMETER EncryptionKey
The encryption key to use to secure the connections file. If no password is supplied the default mRemoteNG encryption key is "mR3m".
.PARAMETER EncryptionEngine
The encryption engine to use when encrypting the connection passwords. Choices are 'AES', 'Serpent', and 'Twofish'. The default is 'AES'.
.PARAMETER BlockCipherMode
The block cipher mode to use when encrypting the connection passwords. Choices are 'GCM', 'CCM', and 'EAX'. The default is 'GCM'.
.PARAMETER KeyDerivationIterations
The number of key derivation iterations to perform when encrypting the connection passwords. Valid values are in the range 1,000 to 50,000.
.PARAMETER SortConnections
Should the exported connections be sorted?
.EXAMPLE
Export-MRNGConnectionFile -RootNode $RootNode -Path .\Connections.xml (Get-Credential).Password -SortConnections
#>
function Export-MRNGConnectionFile {
param(
[Parameter(Mandatory)]
[mRemoteNG.Tree.Root.RootNodeInfo]
$RootNode,
[Parameter(Mandatory)]
[System.IO.FileInfo]
$Path,
[ValidateSet('AES', 'Serpent', 'Twofish')]
[string]
$EncryptionEngine = 'AES',
[ValidateSet('GCM', 'CCM', 'EAX')]
[string]
$BlockCipherMode = 'GCM',
[ValidateRange(1000, 50000)]
[int]
$KeyDerivationIterations = 1000,
[switch]
$SortConnections
)
# resolve the path
$Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $Path )
# get the encryption key as [securestring]
if ( $RootNode.Password ) {
$EncryptionKey = $RootNode.PasswordString |
ConvertTo-SecureString -AsPlainText -Force
} else {
$EncryptionKey = $RootNode.DefaultPassword |
ConvertTo-SecureString -AsPlainText -Force
}
# choose the encryption engine
$Engine = switch ( $EncryptionEngine ) {
'AES' { [Org.BouncyCastle.Crypto.Engines.AesEngine]::new() }
'Serpent' { [Org.BouncyCastle.Crypto.Engines.SerpentEngine]::new() }
'Twofish' { [Org.BouncyCastle.Crypto.Engines.TwofishEngine]::new() }
}
# choose the cipher mode
$Cipher = switch ( $BlockCipherMode ) {
'GCM' { [Org.BouncyCastle.Crypto.Modes.GcmBlockCipher]::new( $Engine ) }
'CCM' { [Org.BouncyCastle.Crypto.Modes.CcmBlockCipher]::new( $Engine ) }
'EAX' { [Org.BouncyCastle.Crypto.Modes.EaxBlockCipher]::new( $Engine ) }
}
# XML serializer
$CryptoProvider = [mRemoteNG.Security.SymmetricEncryption.AeadCryptographyProvider]::new( $Cipher )
$SaveFilter = [mRemoteNG.Security.SaveFilter]::new()
$ConnectionNodeSerializer = [mRemoteNG.Config.Serializers.Xml.XmlConnectionNodeSerializer26]::new($CryptoProvider, $EncryptionKey, $SaveFilter)
$XmlSerializer = [mRemoteNG.Config.Serializers.Xml.XmlConnectionsSerializer]::new($CryptoProvider, $ConnectionNodeSerializer)
# should we sort?
if ( $SortConnections ) {
$RootNode.SortRecursive()
}
# save the connection file
$FilePathProvider = [mRemoteNG.Config.DataProviders.FileDataProvider]::new( $Path )
$FilePathProvider.Save( $XmlSerializer.Serialize( $RootNode ) )
}
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
param(
[string]
$ExportPath = "$env:APPDATA\mRemoteNG\PSmRemoteNG.xml",
[securestring]
$EncryptionKey = ( ConvertTo-SecureString -String 'mR3m' -AsPlainText -Force ),
[hashtable]
$CredentialMap = @{}
)
$ErrorActionPreference = 'Stop'
if ( $CredentialMap.Keys.Count -and ( [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($EncryptionKey)) -eq 'mR3m' ) ) {
throw 'You must supply an encryption key when storing credentials.'
}
#requires -modules PSmRemoteNG
Write-Host 'Generating config file...' -ForegroundColor White
# create the root node with the specified EncryptionKey
# note that you must ALWAYS supply an EncryptionKey, even
# if using the default value 'mR3m' or the config won't open
# in mRemoteNG. Supplying the default value with bypass the
# prompt.
$RootNode = New-MRNGRootNode -EncryptionKey $EncryptionKey
Write-Host 'Searching for AD Forests that your domain trusts...' -ForegroundColor White
# all forests container variable
$Forests = @()
# add your forest
$Forests += @(, (Get-ADForest).Name )
# get all AD Forests that your domain trusts
$Forests += Get-ADTrust -Filter * |
Where-Object { $_.ForestTransitive -eq $true } |
Select-Object -ExpandProperty Target |
Sort-Object
foreach ( $Forest in $Forests ) {
# will hold inheritance for containers if defined
$InheritanceSplat = @{}
# find all domains in the forest
try {
$Domains = Get-ADForest -Server $Forest |
Select-Object -ExpandProperty Domains
} catch {
if ( ( Read-Host ( 'Could not contact forest ''{0}'', continue? [y/N]' -f $Forest ) ) -match '^y' ) {
continue
} else {
throw 'Cancelled'
}
}
# if the forest has more than one domain we make a forest container
if ( $Domains.Count -gt 1 ) {
Write-Host ( 'Create forest container: {0}' -f $Forest ) -ForegroundColor Cyan
$ForestContainer = New-MRNGContainer -Name $Forest -Parent $RootNode
# if the $CredentialMap has a key matching the forest
# we set the credential on the container and turn on
# inheritance
if ( $CredentialMap.Keys -contains $Forest ) {
$ForestContainer.Username = $CredentialMap[$Forest].GetNetworkCredential().Username
$ForestContainer.Domain = $CredentialMap[$Forest].GetNetworkCredential().Domain
$ForestContainer.Password = $CredentialMap[$Forest].GetNetworkCredential().Password
$InheritanceSplat.Inheritance = New-MRNGInheritanceConfiguration -EverythingInherited
}
# otherwise we just use the root node
} else {
$ForestContainer = $RootNode
}
# now we process the domains
foreach ( $Domain in $Domains ) {
Write-Host ( 'Searching for servers in {0}...' -f $Domain ) -ForegroundColor White
# find the domain controller
$DomainController = Get-ADDomainController -DomainName $Domain -Discover -Service ADWS |
Select-Object -ExpandProperty HostName
# look for servers in the domain
$Servers = Get-ADComputer -Filter 'OperatingSystem -like "*Windows Server*"' -Server $DomainController -Properties OperatingSystem, Description, IPv4Address, DNSHostName, Enabled |
Select-Object @{N='Name';E={ $_.Name + (' (disabled)','')[$_.Enabled] }},
@{N='HostName';E={ $_.DNSHostName }},
@{N='Description';E={ ( $_.Description, $_.OperatingSystem )[ [string]::IsNullOrEmpty($_.Description) ] }}
if ( $Servers.Count -eq 0 ) {
Write-Host 'No servers found, skipping...' -ForegroundColor Gray
continue
}
Write-Host ( 'Create domain container: {0}' -f $Domain ) -ForegroundColor DarkCyan
$DomainContainer = New-MRNGContainer -Name $Domain -Parent $ForestContainer @InheritanceSplat
if ( $CredentialMap.Keys -contains $Domain ) {
$DomainContainer.Username = $CredentialMap[$Domain].GetNetworkCredential().Username
$DomainContainer.Domain = $CredentialMap[$Domain].GetNetworkCredential().Domain
$DomainContainer.Password = $CredentialMap[$Domain].GetNetworkCredential().Password
$InheritanceSplat.Inheritance = New-MRNGInheritanceConfiguration -EverythingInherited
}
Write-Host ( 'Creating {0} servers connections...' -f $Servers.Count ) -ForegroundColor DarkGray
foreach ( $Server in $Servers ) {
$ServerInheritanceSplat = @{}
Write-Host ( 'Create server connection: {0}' -f $Server.Name ) -ForegroundColor Gray
if ( -not [string]::IsNullOrEmpty( $Server.Description ) -and $InheritanceSplat.Inheritance ) {
$ServerInheritanceSplat = $InheritanceSplat.Clone()
$ServerInheritanceSplat.Inheritance.Description = $false
}
$ServerConnection = New-MRNGConnection -Name $Server.Name -Hostname $Server.HostName -Description $Server.Description -Parent $DomainContainer @ServerInheritanceSplat
}
}
}
Write-Host ''
if ( -not( Test-Path -Path $ExportPath -PathType Leaf ) -or $PSCmdlet.ShouldProcess( $ExportPath, 'Replace Configuration' ) ) {
Export-MRNGConnectionFile -RootNode $RootNode -Path $ExportPath -SortConnections
Write-Host 'New configuration file path:' -ForegroundColor White
Write-Host $ExportPath -ForegroundColor White
} else {
Write-Host 'Cancelled' -ForegroundColor Red
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment