diff --git a/CHANGELOG.md b/CHANGELOG.md index 9592101..4f56fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,3 +37,9 @@ ## 1.5.2 (Feb 28, 2016) - Refactor Pester tests and Fixtures - Reorganize functions into subfolders + +## 1.6.0 (Jul 15, 2016) +- Enable remote executions with resolving RunspaceIds without specifying -Session parameter +- Add password-masking to Test-PSConnectionString +- Test-PsWebConfig now support -IncludeAppsettings parameters +- Better detailed -Verbose info from all cmdlets diff --git a/Functions/PSAppSetting/Get-PSAppSetting.ps1 b/Functions/PSAppSetting/Get-PSAppSetting.ps1 index 3fc8139..8955e8e 100644 --- a/Functions/PSAppSetting/Get-PSAppSetting.ps1 +++ b/Functions/PSAppSetting/Get-PSAppSetting.ps1 @@ -21,12 +21,15 @@ function Get-PSAppSetting { ) process { + Write-Verbose "Executing Get-PSAppSetting" foreach ($config in $ConfigXml) { + Write-Verbose "Processing configuration '$($config.ComputerName + " " + $config.File)'" if ($config -is [string]) { $config = [xml]$config } if ($config | Get-Member -Name configuration) { if ($config.configuration.appSettings.EncryptedData) { Write-Warning "appSettings section is encrypted. You may not see all relevant entries." + Write-Warning "Execute this command as an administrator for automatic decryption." } foreach ($appSetting in $config.configuration.appSettings.add) { diff --git a/Functions/PSConnectionString/Get-PSConnectionString.ps1 b/Functions/PSConnectionString/Get-PSConnectionString.ps1 index 0eec9aa..837f87f 100644 --- a/Functions/PSConnectionString/Get-PSConnectionString.ps1 +++ b/Functions/PSConnectionString/Get-PSConnectionString.ps1 @@ -29,12 +29,15 @@ function Get-PSConnectionString { ) process { + Write-Verbose "Executing Get-PSConnectionString" foreach ($config in $ConfigXml) { if ($config -is [string]) { $config = [xml]$config } if ($config | Get-Member -Name configuration) { + Write-Verbose "Processing configuration '$($config.ComputerName + " " + $config.File)'" if ($config.configuration.connectionStrings.EncryptedData) { Write-Warning "ConnectionStrings section is encrypted. You may not see all relevant entries." + Write-Warning "Execute this command as an administrator for automatic decryption." } foreach ($connectionString in $config.configuration.connectionStrings.add) { @@ -46,8 +49,12 @@ function Get-PSConnectionString { Set_Type -TypeName "PSWebConfig.ConnectionString" } - if (-Not $IncludeAppSettings) { continue } + if (-Not $IncludeAppSettings) { + Write-Verbose "Appsettings are not included in ConnectionString processing." + continue + } + Write-Verbose "Looking for ConnectionStrings in appsettings.." $config | Get-PSAppSetting | ForEach-Object { if ($_.value -match 'data source=') { $_ | diff --git a/Functions/PSConnectionString/Test-PSConnectionString.ps1 b/Functions/PSConnectionString/Test-PSConnectionString.ps1 index 4f9d15d..7247de1 100644 --- a/Functions/PSConnectionString/Test-PSConnectionString.ps1 +++ b/Functions/PSConnectionString/Test-PSConnectionString.ps1 @@ -14,6 +14,9 @@ If -ReplaceRules hashtable is specified it will replace hash-keys with it's values in the ConnectionString to be tested. + Passwords in the ConnectionString are masked by default, use -ShowPassword + switch if you need to show passwords in test-results. + .PARAMETER InputObject Mandatory - Pipeline input of PsConnectionString @@ -24,6 +27,9 @@ Optional - Hashtable that replaces hash-keys with it's values in the ConnectionString to be tested. +.PARAMETER ShowPassword + Optional - Switch to disable password masking for the test result. + .PARAMETER Session Optional - PSSession to execute the test-against it. @@ -42,10 +48,13 @@ function Test-PSConnectionString { [string]$ConnectionString, [hashtable]$ReplaceRules, - [System.Management.Automation.Runspaces.PSSession[]]$Session + [System.Management.Automation.Runspaces.PSSession[]]$Session, + + [switch]$ShowPassword ) process { + Write-Verbose "Executing Test-PSConnectionString" if ($ConnectionString) { $InputObject = New-Object -TypeName PsObject -Property @{ ConnectionString = $ConnectionString @@ -59,16 +68,19 @@ function Test-PSConnectionString { $EntrySession = $entry.Session if ($Session) { $EntrySession = $Session } + $ArgumentList = $entry.ConnectionString,$ReplaceRules,$ShowPassword if ($EntrySession) { + Write-Verbose "Remote Test execution from '$($EntrySession.ComputerName)'" Invoke-Command ` -Session $EntrySession ` - -ArgumentList $entry.ConnectionString,$ReplaceRules ` + -ArgumentList $ArgumentList ` -ScriptBlock $Function:Test_ConnectionString | Add-Member -NotePropertyName Session -NotePropertyValue $EntrySession -Force -PassThru | Set_Type -TypeName 'PSWebConfig.TestResult' } else { + Write-Verbose "Local Test execution" Invoke-Command ` - -ArgumentList $entry.ConnectionString,$ReplaceRules ` + -ArgumentList $ArgumentList ` -ScriptBlock $Function:Test_ConnectionString | Set_Type -TypeName 'PSWebConfig.TestResult' } diff --git a/Functions/PSConnectionString/Test_ConnectionString.ps1 b/Functions/PSConnectionString/Test_ConnectionString.ps1 index 97749fa..6aee5f2 100644 --- a/Functions/PSConnectionString/Test_ConnectionString.ps1 +++ b/Functions/PSConnectionString/Test_ConnectionString.ps1 @@ -4,7 +4,10 @@ function Test_ConnectionString { [string]$ConnectionString, [Parameter(Position=1)] - [hashtable]$ReplaceRules + [hashtable]$ReplaceRules, + + [Parameter(Position=2)] + [bool]$ShowPassword=$false ) $result = New-Object PsObject -Property @{ @@ -55,5 +58,13 @@ function Test_ConnectionString { } finally { if ($SqlConnection) { $SqlConnection.Close() } } + + if (!$ShowPassword) { + Write-Verbose "Masking password due to ShowPassword:false .." + $maskRule = ';\s*password=(\S+)\s*(;|$)',';password=***;' + $result.ConnectionString = $result.ConnectionString -ireplace $maskRule + $result.Test = $result.Test -ireplace $maskRule + } + return $result } diff --git a/Functions/PSUri/Get-PSEndpoint.ps1 b/Functions/PSUri/Get-PSEndpoint.ps1 index ce3e5e0..35fe326 100644 --- a/Functions/PSUri/Get-PSEndpoint.ps1 +++ b/Functions/PSUri/Get-PSEndpoint.ps1 @@ -21,7 +21,9 @@ function Get-PSEndpoint { ) process { + Write-Verbose "Executing Get-PSEndpoint" foreach ($config in $ConfigXml) { + Write-Verbose "Processing configuration '$($config.ComputerName + " " + $config.File)'" if ($config | Get-Member -Name configuration) { foreach ($endpoint in $config.configuration.'system.serviceModel'.client.endpoint) { $endpoint | @@ -32,6 +34,8 @@ function Get-PSEndpoint { Add-Member -MemberType AliasProperty -Name Uri -Value address -Force -PassThru | Set_Type -TypeName 'PSWebConfig.Uri' } + } else { + Write-Warning "Skipping invalid configuration: $config" } } } diff --git a/Functions/PSUri/Get-PSUri.ps1 b/Functions/PSUri/Get-PSUri.ps1 index f740397..61ca6f9 100644 --- a/Functions/PSUri/Get-PSUri.ps1 +++ b/Functions/PSUri/Get-PSUri.ps1 @@ -9,6 +9,8 @@ .PARAMETER ConfigXml Mandatory - Pipeline input for Configuration XML +.PARAMETER IncludeAppSettings + Optional - Switch to include http/s URIs from appsettings .EXAMPLE Get-PSWebConfig -Path 'C:\inetpub\wwwroot\myapp' | Get-PSUri @@ -19,19 +21,27 @@ function Get-PSUri { [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipeLine=$true)] - [psobject[]]$ConfigXml + [psobject[]]$ConfigXml, + [switch]$IncludeAppSettings ) process { - # Return all service endpoint addresses + Write-Verbose "Executing Get-PSUri" + + Write-Verbose "Looking for service-endpoint addresses..." Get-PSEndpoint -ConfigXml $configXml - # Return any URL from appSettings as an Address - Get-PSAppSetting -ConfigXml $configXml | - Where-Object value -imatch '^http[s]*:' | - Add-Member -MemberType AliasProperty -Name name -Value key -Force -PassThru | - Add-Member -MemberType AliasProperty -Name address -Value value -Force -PassThru | - Add-Member -MemberType AliasProperty -Name Uri -Value value -Force -PassThru | - Set_Type -TypeName 'PSWebConfig.Uri' + if (-Not $IncludeAppSettings) { + Write-Verbose "Appsettings are not included in URI processing." + return + } else { + Write-Verbose "Looking for any http URLs from appSettings..." + Get-PSAppSetting -ConfigXml $configXml | + Where-Object value -imatch '^http[s]*:' | + Add-Member -MemberType AliasProperty -Name name -Value key -Force -PassThru | + Add-Member -MemberType AliasProperty -Name address -Value value -Force -PassThru | + Add-Member -MemberType AliasProperty -Name Uri -Value value -Force -PassThru | + Set_Type -TypeName 'PSWebConfig.Uri' + } } } diff --git a/Functions/PSUri/Test-PSUri.ps1 b/Functions/PSUri/Test-PSUri.ps1 index 1169f94..3dd0e02 100644 --- a/Functions/PSUri/Test-PSUri.ps1 +++ b/Functions/PSUri/Test-PSUri.ps1 @@ -71,6 +71,8 @@ function Test-PSUri { ) process { + Write-Verbose "Executing Test-PSUri" + if ($Uri) { $InputObject = $Uri | Foreach-Object { New-Object -TypeName PsObject -Property @{ diff --git a/Functions/PSWebConfig/Get-PSWebConfig.ps1 b/Functions/PSWebConfig/Get-PSWebConfig.ps1 index 4e94a88..ae4534a 100644 --- a/Functions/PSWebConfig/Get-PSWebConfig.ps1 +++ b/Functions/PSWebConfig/Get-PSWebConfig.ps1 @@ -8,6 +8,11 @@ It accepts either Path or an InputObject to discover the configuration files and if -Recurse is specified it discovers all sub-configuration too. + Remote configurations can be fetched by setting -Session parameter. + + If the input object is received from a PSSession instance, it will try to + use the session's InstanceId to fetch the configuration remotely. + .PARAMETER InputObject Mandatory - Parameter to pass the Application or WebSite from pipeline .PARAMETER Path @@ -67,7 +72,11 @@ function Get-PSWebConfig { [System.Management.Automation.Runspaces.PSSession]$Session ) process { - if (!$AsText -and !$AsFileInfo) { $AsXml = $true } + Write-Verbose "Executing Get-PSWebConfig" + if (!$AsText -and !$AsFileInfo) { + Write-Verbose "Defaulting output-format to XML object" + $AsXml = $true + } if ($Path) { Write-Verbose "Processing by Path" @@ -80,25 +89,32 @@ function Get-PSWebConfig { if ($InputObject) { Write-Verbose "Processing by InputObject" foreach ($entry in $InputObject) { + # Setting Remote Session + $EntrySession = $entry.Session + if ($Session) { + Write-Verbose "Overriding session from -Session Parameter" + $EntrySession = $Session + } + elseif ($entry | Get-Member -Name RunspaceId) { + Write-Verbose "Getting Session from RunspaceId '$($entry.RunspaceId)'" + $EntrySession = Get-PSSession -InstanceId $entry.RunspaceId + } - if ($entry -is [System.IO.FileInfo]) { + if ($entry -is [System.IO.FileInfo] -or $entry.psobject.TypeNames -icontains 'Deserialized.System.IO.FileInfo') { Write-Verbose "Adding physicalPath alias for [System.IO.FileInfo] FullName" $entry = $entry | Add-Member -MemberType AliasProperty -Name physicalPath -Value FullName -PassThru } if ($entry | Get-Member -Name physicalPath) { - $EntrySession = $entry.Session - if ($Session) { $EntrySession = $Session } - if ($EntrySession) { - Write-Verbose "Remote Invoke-Command to '$($EntrySession.ComputerName)'" + Write-Verbose "Remote configuration fetch from '$($EntrySession.ComputerName + " " + $entry.physicalPath)'" $response = Invoke-Command ` -Session $EntrySession ` -ArgumentList @($entry.physicalPath, $AsFileInfo, $AsText, $Recurse) ` -ScriptBlock ${function:Get_ConfigFile} | Add-Member -NotePropertyName Session -NotePropertyValue $EntrySession -Force -PassThru } else { - Write-Verbose "Local Invoke-Command" + Write-Verbose "Local configuration fetch from '$($entry.physicalPath)'" $response = Invoke-Command ` -ArgumentList @($entry.physicalPath, $AsFileInfo, $AsText, $Recurse) ` -ScriptBlock ${function:Get_ConfigFile} diff --git a/Functions/PSWebConfig/Test-PSWebConfig.ps1 b/Functions/PSWebConfig/Test-PSWebConfig.ps1 index 4d9e4e6..99f9e32 100644 --- a/Functions/PSWebConfig/Test-PSWebConfig.ps1 +++ b/Functions/PSWebConfig/Test-PSWebConfig.ps1 @@ -3,8 +3,12 @@ Tests all URI and ConnectionStrings from web or application configuration. .DESCRIPTION - The cmdlet fetches all ConnectionString and URIs for a configuration file - and executes a test against them on a local or remote machine. + The cmdlet fetches all ConnectionString and service endpoint URIs + from a configuration xml and executes a test against them on a + local or remote machine. + + If -IncludeAppSettings switch is defined, it will include any URI or + ConnectionStrings to the tests. .PARAMETER InputObject Mandatory - Parameter to pass a PSWebConfig object @@ -12,6 +16,10 @@ .PARAMETER Session Optional - PSSession to execute configuration test +.PARAMETER IncludeAppSettings + Optional - Switch to include URIs and ConnectionStrings from appSettings + sections + .EXAMPLE Get-PSWebConfig -Path 'c:\intepub\wwwroot\testapp\' | Test-PSWebConfig @@ -25,13 +33,16 @@ function Test-PSWebConfig { [Parameter(ValueFromPipeLine=$true)] [psobject[]]$ConfigXml, + [switch]$IncludeAppSettings, [System.Management.Automation.Runspaces.PSSession]$Session ) process { - Get-PSEndpoint -ConfigXml $ConfigXml | - Test-PSUri -Session $Session + Write-Verbose "Executing Test-PSWebConfig" + + Get-PSUri -ConfigXml $ConfigXml -IncludeAppSettings:$IncludeAppSettings | + Test-PSUri -Session $Session - Get-PSConnectionString -ConfigXml $ConfigXml | - Test-PSConnectionString -Session $Session + Get-PSConnectionString -ConfigXml $ConfigXml -IncludeAppSettings:$IncludeAppSettings | + Test-PSConnectionString -Session $Session } } diff --git a/PSWebConfig.psd1 b/PSWebConfig.psd1 index 59775e6..015ad99 100644 --- a/PSWebConfig.psd1 +++ b/PSWebConfig.psd1 @@ -10,7 +10,7 @@ RootModule = 'PSWebConfig.psm1' # Version number of this module. -ModuleVersion = '1.5.2.0' +ModuleVersion = '1.6.0.0' # ID used to uniquely identify this module GUID = '37abef2c-d883-46be-ce1a-53d16477d01d' diff --git a/README.md b/README.md index 4ce35e1..163bea1 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,17 @@ PSWebConfig PowerShell module [![Build status](https://ci.appveyor.com/api/projects/status/4tcovid4e04m1vdx?svg=true)](https://ci.appveyor.com/project/muratiakos/pswebconfig) -PSWebConfig is a PowerShell module that provides an easy way to decrypt and -inspect and test web.config or application configuration files locally or remotely. +PSWebConfig is a PowerShell module that provides an easy way to automatically decrypt, +inspect and test web.config or any .NET based application configuration files both +locally or remotely. ## Installation -PSWebConfig is available via [PsGet][psget], so you can simply install it with the -following command: +PSWebConfig is available via [PowerShell Gallery][PowerShellGallery] or [PsGet][psget], +so you can simply install it with the following command: ```powershell -# Install it from PsGet Install-Module PSWebConfig -# Or install it from this repository +# Or alternatevely you can install it with PsGet from this repository Install-Module -ModuleUrl https://github.com/muratiakos/PSWebConfig/archive/master.zip ``` Of course you can download and install the module manually too from @@ -26,13 +26,27 @@ Import-Module PSWebConfig ## Examples ### View and decrypt a web.config +`Get-PSWebConfig` cmdlet automatically fetches and decrypts any web.config +file both locally and remotely without altering the actual config file on the +target computer: ```powershell -# You can pipe any site into Get-PSWebConfig +# You can pipe any site into Get-PSWebConfig to decrypt it automatically Get-Website * | Get-PSWebConfig -AsText # You can use -Path attribute to find web.config files Get-PSWebConfig -Path C:\inetpub\wwwroot\ ``` +### Test config files +`Test-PSWebConfig` function allows complete tests on all connectionStrings and +Service addresses from a configuration object both on local or remote computers. +```powershell +# Pipe Get-PSWebConfig into Test-PSWebConfig +Get-Website * | Get-PSWebConfig | Test-PSWebConfig + +# Or use -Session to test it via remote PSSession +$server1 = New-PSSession 'server1.local.domain' +Get-PSWebConfig -Path C:\inetpub\wwwroot\ -Session $server1 | Test-PSWebConfig +``` ### Inspect ConnectionStrings ```powershell @@ -44,6 +58,8 @@ Get-PSWebConfig -Path C:\inetpub\wwwroot\ | Get-PSConnectionString -IncludeAppSe ``` ### Test ConnectionStrings +`Test-PSConnectionString` cmdlet tries to initiate a SQL connection from a local or +remote computer to test if there are any issue connection to a database. ```powershell # Pipe Get-PSConnectionString to Test-PSConnectionString Get-Website * | Get-PSWebConfig | Get-PSConnectionString -Inc | Test-PSConnectionString @@ -67,19 +83,6 @@ Get-Website * | Get-PSWebConfig | Get-PSEndpoint Get-Website * | Get-PSWebConfig | Get-PSUri ``` -### Test config files completely -`Test-PSWebConfig` function allows complete tests on all connectionStrings and -Service addresses from a configuration object both on local or remote computers. -```powershell -# Pipe Get-PSWebConfig object into Test-PSWebConfig -Get-Website * | Get-PSWebConfig | Test-PSWebConfig - -# Or use -Session to test it via remote PSSession -$server1 = New-PSSession 'server1.local.domain' -Get-PSWebConfig -Path C:\inetpub\wwwroot\ | Test-PSWebConfig -Session $server1 -``` - - Call `help` on any of the PSWebConfig cmdlets for more information and examples. ## Documentation @@ -119,4 +122,5 @@ Apache License, Version 2.0 (see [LICENSE][LICENSE]) [license]: LICENSE [semver]: http://semver.org/ [psget]: http://psget.net/ +[PowerShellGallery]: https://www.powershellgallery.com/packages/PSWebConfig [download]: https://github.com/muratiakos/PSWebConfig/archive/master.zip diff --git a/Tests/Fixtures/web.config b/Tests/Fixtures/web.config index 23ea1a4..31a84a3 100644 --- a/Tests/Fixtures/web.config +++ b/Tests/Fixtures/web.config @@ -12,7 +12,7 @@ - + diff --git a/Tests/PSConnectionString/Test_ConnectionString.tests.ps1 b/Tests/PSConnectionString/Test_ConnectionString.tests.ps1 index c0d79a8..b803185 100644 --- a/Tests/PSConnectionString/Test_ConnectionString.tests.ps1 +++ b/Tests/PSConnectionString/Test_ConnectionString.tests.ps1 @@ -8,14 +8,15 @@ Describe "Test_ConnectionString helper function" { @{ Invalid='IvServer=localhost;IvDatabase=##DB##;Connection Timeout=1' - NonExisting='Server=localhost;Database=##DB##ThatShouldNotExist;User Id=uname;Password=xxx;Connection Timeout=1;' + NonExisting="Server=localhost;Database=##DB##ThatShouldNotExist;User Id=uname;Password=4bCd;Connection Timeout=1;" + NonExistingWithPasswordEnd="Server=localhost;Database=##DB##ThatShouldNotExist;User Id=uname;Connection Timeout=1;Password=4bCd" }.GetEnumerator() | ForEach-Object { context "$($_.Key) SqlConnectionString" { $failingConnectionString=$_.Value It "Should have failed test result properties" { - $result = Test_ConnectionString -ConnectionString $failingConnectionString -Verbose:$isVerbose -EA 0 + $result = Test_ConnectionString -ConnectionString $failingConnectionString -Verbose:$isVerbose -ShowPassword $true -EA 0 $result | Should Not BeNullOrEmpty $result.ComputerName | Should Be ([System.Net.Dns]::GetHostByName($env:COMPUTERNAME).HostName) $result.TestType | Should Be 'SqlTest' @@ -29,11 +30,20 @@ Describe "Test_ConnectionString helper function" { It "Should replace ConnectionString with ReplaceRules" { $replacedFailingConnectionString=$failingConnectionString -replace '##DB##','DB_SUBST' $replaceRule = @{'##DB##'='DB_SUBST'} - $result = Test_ConnectionString -ConnectionString $failingConnectionString -ReplaceRules $replaceRule -Verbose:$isVerbose -EA 0 + $result = Test_ConnectionString -ConnectionString $failingConnectionString -ReplaceRules $replaceRule -Verbose:$isVerbose -ShowPassword $true -EA 0 $result.Test | Should Be $failingConnectionString $result.ConnectionString | Should Be $replacedFailingConnectionString } + + if ($failingConnectionString -imatch "Password=") { + It "Should replace replace Password= field to ***" { + $result = Test_ConnectionString -ConnectionString $failingConnectionString -Verbose:$isVerbose -EA 0 + + $result.Test | Should Match 'Password=\*\*\*' + $result.ConnectionString | Should Match 'Password=\*\*\*' + } + } } } } diff --git a/Tests/PSUri/Get-PSUri.tests.ps1 b/Tests/PSUri/Get-PSUri.tests.ps1 index 19434a0..3d04ba7 100644 --- a/Tests/PSUri/Get-PSUri.tests.ps1 +++ b/Tests/PSUri/Get-PSUri.tests.ps1 @@ -7,9 +7,10 @@ $webConfigFile = Join-Path $script:FixturePath 'web.config' Describe 'Get-PSUri' { Context 'Local web.config' { $config = Get-PSWebConfig -Path $webConfigFile -Verbose:$isVerbose - $addresses = $config | Get-PSUri -Verbose:$isVerbose + $addresses = $config | Get-PSUri -Verbose:$isVerbose -IncludeAppsettings + $endpoints = (Get-PSEndpoint -ConfigXml $config -Verbose:$isVerbose) - It 'should return all addresses' { + It 'should return all addresses with -IncludeAppsettings' { $addresses | Should Not BeNullOrEmpty $addresses.Count | Should Be (2+2) # appSetting + endpoints $addresses | Foreach-Object { @@ -20,5 +21,18 @@ Describe 'Get-PSUri' { $_.Uri | Should Match '^http[s]*:' } } + + It 'should contain all service endpoints' { + $addresses | Should Not BeNullOrEmpty + foreach ($e in $endpoints) { + $addresses -contains $e | Should Be $true + } + } + + It 'should be the same as Get-PSEndpoint without -IncludeAppsettings' { + $addressesWithouAppSettings = $config | Get-PSUri -Verbose:$isVerbose + $addressesWithouAppSettings | Should Not BeNullOrEmpty + $addressesWithouAppSettings | Should Be $endpoints + } } }