├── DSC └── cIniFile │ ├── cIniFile.psd1 │ ├── Examples │ ├── Example 2 - Using composite resource │ │ ├── cIniFile_config_example.ps1 │ │ └── cIniFileResources │ │ │ ├── cIniFileResources.psd1 │ │ │ └── DSCResources │ │ │ └── cIniFile2 │ │ │ ├── cIniFile2.psd1 │ │ │ └── cIniFile2.schema.psm1 │ └── Example 1 - Using DSC config │ │ └── cIniFile_Example.ps1 │ └── cIniFile.psm1 ├── Modules ├── IniManager │ ├── IniManager.psd1 │ ├── README.md │ └── IniManager.psm1 ├── Get-ParamHelp │ ├── Get-ParamHelp.psd1 │ ├── README.md │ ├── Get-ParamHelp.psm1 │ ├── data.json.tests.ps1 │ ├── data.json │ ├── Get-ParamHelp.ps1 │ └── Get-ParamHelp.tests.ps1 ├── Write-ObjectToSQL │ ├── Write-ObjectToSQL.psd1 │ └── README.md ├── Edit-Command │ ├── Edit-Command.Tests.ps1 │ ├── Edit-Command.ps1 │ └── README.md ├── Get-IMDBmovie │ ├── README.md │ └── Get-IMDBmovie.psm1 └── WeatherForecast │ ├── README.md │ └── WeatherForecast.ps1 ├── Scripts ├── Ping many from many │ └── PingManyFromMany.ps1 ├── Get-FileCacheSize │ └── Get-FileCacheSize.ps1 └── Find-Hotfix │ └── Find-Hotfix.ps1 └── README.md /DSC/cIniFile/cIniFile.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnRoos/PowerShell/HEAD/DSC/cIniFile/cIniFile.psd1 -------------------------------------------------------------------------------- /Modules/IniManager/IniManager.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnRoos/PowerShell/HEAD/Modules/IniManager/IniManager.psd1 -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/Get-ParamHelp.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnRoos/PowerShell/HEAD/Modules/Get-ParamHelp/Get-ParamHelp.psd1 -------------------------------------------------------------------------------- /Modules/Write-ObjectToSQL/Write-ObjectToSQL.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnRoos/PowerShell/HEAD/Modules/Write-ObjectToSQL/Write-ObjectToSQL.psd1 -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/README.md: -------------------------------------------------------------------------------- 1 | # Get-ParamHelp 2 | This module contains a few cmdlets to make it easier to get help about parameter validation when writing advanced functions in PowerShell. 3 | -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/Get-ParamHelp.psm1: -------------------------------------------------------------------------------- 1 | # import all parameter validation functions 2 | foreach ($function in (Get-ChildItem "$PSScriptRoot\Get-ParamHelp.ps1")) 3 | { 4 | $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($function))), $null, $null) 5 | } 6 | -------------------------------------------------------------------------------- /DSC/cIniFile/Examples/Example 2 - Using composite resource/cIniFile_config_example.ps1: -------------------------------------------------------------------------------- 1 | Configuration MyIniFile 2 | { 3 | Import-DscResource -ModuleName cIniFileResources 4 | 5 | cIniFile2 MyFile 6 | { 7 | Path = 'c:\temp\inifile.ini' 8 | EnableDebug = 'true' 9 | LogPath = 'C:\logfile.log' 10 | } 11 | } 12 | 13 | # MyIniFile -OutputPath c:\temp\MyIniFile\ 14 | # Start-DscConfiguration -Path C:\temp\MyIniFile\ -Wait -Verbose -Force -------------------------------------------------------------------------------- /DSC/cIniFile/Examples/Example 1 - Using DSC config/cIniFile_Example.ps1: -------------------------------------------------------------------------------- 1 | configuration MyIniConfig 2 | { 3 | Import-DscResource -ModuleName cIniFile 4 | cIniFile MyFile 5 | { 6 | Path = 'c:\temp\file.ini' 7 | Config = @{ 8 | '[Config]Debug' = 'true' 9 | '[Config]Logpath' = 'c:\temp\log.txt' 10 | } 11 | } 12 | } 13 | 14 | # MyIniConfig -OutputPath c:\temp\MyIniConfig\ 15 | # Start-DscConfiguration -Path C:\temp\MyIniConfig\ -Wait -Verbose -Force 16 | -------------------------------------------------------------------------------- /Modules/Edit-Command/Edit-Command.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | Describe "Testing Edit-Command" { 3 | 4 | Mock OpenTab { 5 | param ( [System.Text.StringBuilder] $code ) 6 | Return 'Mocked' 7 | } 8 | 9 | It "Opens the help command in a new tab" { 10 | Edit-Command -Name help | Should Be 'Mocked' 11 | } 12 | 13 | It "Throws error when trying to open the heeeelp command" { 14 | { Edit-Command -Name heeeelp } | Should Throw 15 | } 16 | 17 | It "Throws error when *help* is used" { 18 | { Edit-Command -Name *help* } | Should Throw 19 | } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /DSC/cIniFile/Examples/Example 2 - Using composite resource/cIniFileResources/cIniFileResources.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'cIniFileResources' 3 | # 4 | # Generated by: John Roos 5 | # 6 | # Generated on: 2016-03-28 7 | # 8 | 9 | @{ 10 | 11 | # Version number of this module. 12 | ModuleVersion = '0.1' 13 | 14 | # ID used to uniquely identify this module 15 | GUID = 'fa7d1e48-a6bd-47b5-bee4-a0a2f1f7ff02' 16 | 17 | # Author of this module 18 | Author = 'John Roos' 19 | 20 | # Functions to export from this module 21 | FunctionsToExport = '*' 22 | 23 | # Cmdlets to export from this module 24 | CmdletsToExport = '*' 25 | 26 | } -------------------------------------------------------------------------------- /DSC/cIniFile/Examples/Example 2 - Using composite resource/cIniFileResources/DSCResources/cIniFile2/cIniFile2.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'cIniFile2' 3 | # 4 | # Generated by: John Roos 5 | # 6 | # Generated on: 2016-03-28 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'cIniFile2.schema.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.1' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'ea7249e0-9ba9-45c9-a441-675e2c8a683b' 19 | 20 | # Author of this module 21 | Author = 'John Roos' 22 | 23 | # Functions to export from this module 24 | FunctionsToExport = '*' 25 | 26 | # Cmdlets to export from this module 27 | CmdletsToExport = '*' 28 | 29 | } -------------------------------------------------------------------------------- /Modules/Get-IMDBmovie/README.md: -------------------------------------------------------------------------------- 1 | # Get-IMDBmovie 2 | This module contains one cmdlet for getting information about movies from IMDB. 3 | ``` 4 | PS C:\> Get-ImdbMovie -Title 'Star Trek' 5 | 6 | Title : Star Trek Into Darkness 7 | Directors : J.J. Abrams 8 | Year : 2013 9 | Rating : 7.8 10 | Description : After the crew of the Enterprise find an unstoppable force of terror from within their 11 | own organization, Captain Kirk leads a manhunt to a war-zone world to capture a one-man 12 | weapon of mass destruction. 13 | Writers : Roberto Orci, Alex Kurtzman, 2 more credits 14 | Stars : Chris Pine, Zachary Quinto, Zoe Saldana 15 | Link : http://www.imdb.com/title/tt1408101/ 16 | 17 | PS C:\> 18 | ``` 19 | More information can be found here: http://blog.roostech.se/2015/03/powershell-get-imdbmovie-using-invoke.html 20 | -------------------------------------------------------------------------------- /DSC/cIniFile/Examples/Example 2 - Using composite resource/cIniFileResources/DSCResources/cIniFile2/cIniFile2.schema.psm1: -------------------------------------------------------------------------------- 1 | Configuration cIniFile2 2 | { 3 | param 4 | ( 5 | # Path to ini file 6 | [Parameter(Mandatory)] 7 | [ValidateNotNullOrEmpty()] 8 | [String] $Path, 9 | 10 | # Debug mode (true or false) 11 | [Parameter(Mandatory)] 12 | [ValidateNotNullOrEmpty()] 13 | [ValidateSet("true", "false")] 14 | [String] $EnableDebug, 15 | 16 | # Logfile path 17 | [Parameter(Mandatory)] 18 | [ValidateNotNullOrEmpty()] 19 | [String] $LogPath 20 | ) 21 | 22 | Import-DscResource -ModuleName cIniFile 23 | cIniFile MyFile 24 | { 25 | Path = $Path 26 | Config = @{ 27 | '[Config]Debug' = $EnableDebug 28 | '[Config]Logpath' = $LogPath 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Scripts/Ping many from many/PingManyFromMany.ps1: -------------------------------------------------------------------------------- 1 | # Created by John Roos 2 | # Email: john@roostech.se 3 | # Web: http://blog.roostech.se 4 | 5 | $sources = 'server1', 'server2', 'server3' 6 | $targets = 'server4', 'server5' 7 | $logpath = 'C:\Ping_logs\' 8 | 9 | $pingscript = { 10 | while ($true) { 11 | $pingdate = Get-Date -Format u 12 | $logpath = "$($args[2])$($args[0])_$($args[1])_$(Get-Date -f yyyy-MM-dd).log" 13 | Try { 14 | Test-Connection -Count 1 -ErrorAction Stop -Source $($args[0]) -ComputerName $($args[1]) ` 15 | | Select-Object {$pingdate}, __SERVER, Address, ResponseTime ` 16 | | ConvertTo-Csv ` 17 | | Select-Object -Skip 2 ` 18 | | Out-File -FilePath $logpath -Append 19 | } catch { 20 | $responseprops = [ordered]@{ 21 | 'datetime' = $pingdate 22 | '__SERVER' = $args[0] 23 | 'Address' = $args[1] 24 | 'ResponseTime' = '9999' 25 | } 26 | $response = New-Object psobject -Property $responseprops 27 | ConvertTo-Csv -InputObject $response ` 28 | | Select-Object -Skip 2 ` 29 | | Out-File -FilePath $logpath -Append 30 | } 31 | Start-Sleep -Seconds 1 32 | } 33 | } 34 | 35 | foreach ($source in $sources){ 36 | foreach ($target in $targets){ 37 | Start-Job -Name "$source ping $target" -ScriptBlock $pingscript -ArgumentList $source, $target, $logpath 38 | } 39 | } -------------------------------------------------------------------------------- /Modules/Edit-Command/Edit-Command.ps1: -------------------------------------------------------------------------------- 1 | function Edit-Command { 2 | 3 | [CmdletBinding()] 4 | [OutputType([void])] 5 | 6 | param ( 7 | [validatenotnullorempty()] 8 | [string]$Name 9 | ) 10 | 11 | Process 12 | { 13 | try { 14 | $command = Get-Command -Name $Name -ErrorAction Stop 15 | } catch { 16 | Throw $Error[0].Exception 17 | } 18 | 19 | if ( $command -is [array]) { 20 | throw 'Multiple commands found. You need to be more specific.' 21 | } 22 | 23 | if (-not ($scriptblock = $command.ScriptBlock) ) { 24 | $exception = "The command does not have a scriptblock available." 25 | Throw $exception 26 | } 27 | 28 | $code = New-Object -TypeName System.Text.StringBuilder 29 | 30 | $code.Append("# Command: $($command.Name)`n") | Out-Null 31 | $code.Append("# Type: $($command.CommandType)`n") | Out-Null 32 | $code.Append("# Version: $($command.Version)`n") | Out-Null 33 | $code.Append("# Source: $($command.Source)`n") | Out-Null 34 | $code.Append($scriptblock) | Out-Null 35 | 36 | OpenTab -code $code 37 | 38 | } 39 | } 40 | 41 | function OpenTab { 42 | param ( [System.Text.StringBuilder]$code ) 43 | 44 | $displayname = $psISE.CurrentPowerShellTab.Files.Add().DisplayName 45 | $openfile = $psISE.PowerShellTabs.files | Where-Object DisplayName -eq $displayname | Select-Object -First 1 46 | 47 | $openfile.Editor.Text += $code 48 | $openfile.Editor.SetCaretPosition(1,1) 49 | 50 | } -------------------------------------------------------------------------------- /Modules/Edit-Command/README.md: -------------------------------------------------------------------------------- 1 | # Edit-Command 2 | This module contains a cmdlet which can be used to edit other cmdlets in PowerShell ISE. 3 | 4 | Did you know that you can see the actual source of hundreds of built in cmdlets? 5 | ## Example 6 | On my system I currently have 1855 cmdlets: 7 | ``` 8 | PS C:\> (get-command).count 9 | 1855 10 | ``` 11 | 948 of those are written in PowerShell and can be edited. 12 | ``` 13 | PS C:\> (get-command | Where-Object ScriptBlock).count 14 | 948 15 | ``` 16 | Lets try one. 17 | ``` 18 | PS C:\> Edit-Command -Name Get-Verb 19 | ``` 20 | Okay, so its a bit hard to show that this actually opens a new tab in the ISE with the code of Get-Verb, but here is the code it shows: 21 | ``` 22 | # Command: Get-Verb 23 | # Type: Function 24 | # Version: 25 | # Source: 26 | 27 | param( 28 | [Parameter(ValueFromPipeline=$true)] 29 | [string[]] 30 | $verb = '*' 31 | ) 32 | begin { 33 | $allVerbs = [System.Reflection.IntrospectionExtensions]::GetTypeInfo([PSObject]).Assembly.ExportedTypes | 34 | Microsoft.PowerShell.Core\Where-Object {$_.Name -match '^Verbs.'} | 35 | Microsoft.PowerShell.Utility\Get-Member -type Properties -static | 36 | Microsoft.PowerShell.Utility\Select-Object @{ 37 | Name='Verb' 38 | Expression = {$_.Name} 39 | }, @{ 40 | Name='Group' 41 | Expression = { 42 | $str = "$($_.TypeName)" 43 | $str.Substring($str.LastIndexOf('Verbs') + 5) 44 | } 45 | } 46 | } 47 | process { 48 | foreach ($v in $verb) { 49 | $allVerbs | Microsoft.PowerShell.Core\Where-Object { $_.Verb -like $v } 50 | } 51 | } 52 | # .Link 53 | # http://go.microsoft.com/fwlink/?LinkID=160712 54 | # .ExternalHelp System.Management.Automation.dll-help.xml 55 | ``` 56 | Pretty cool. 57 | -------------------------------------------------------------------------------- /Scripts/Get-FileCacheSize/Get-FileCacheSize.ps1: -------------------------------------------------------------------------------- 1 | # Credit goes to BeowulfNode42 on Stackoverflow. Thank you. 2 | # http://stackoverflow.com/questions/5898843/c-sharp-get-system-file-cache-size 3 | 4 | $source = @" 5 | using System; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace MyTools 9 | { 10 | public static class cache 11 | { 12 | 13 | [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] 14 | public static extern bool GetSystemFileCacheSize( 15 | ref IntPtr lpMinimumFileCacheSize, 16 | ref IntPtr lpMaximumFileCacheSize, 17 | ref IntPtr lpFlags 18 | ); 19 | 20 | public static bool Get( ref IntPtr a, ref IntPtr c, ref IntPtr d ) 21 | { 22 | IntPtr lpMinimumFileCacheSize = IntPtr.Zero; 23 | IntPtr lpMaximumFileCacheSize = IntPtr.Zero; 24 | IntPtr lpFlags = IntPtr.Zero; 25 | 26 | bool b = GetSystemFileCacheSize(ref lpMinimumFileCacheSize, ref lpMaximumFileCacheSize, ref lpFlags); 27 | 28 | a = lpMinimumFileCacheSize; 29 | c = lpMaximumFileCacheSize; 30 | d = lpFlags; 31 | return b; 32 | } 33 | } 34 | } 35 | "@ 36 | 37 | Add-Type -TypeDefinition $source -Language CSharp 38 | 39 | # Init variables 40 | $SFCMin = 0 41 | $SFCMax = 0 42 | $SFCFlags = 0 43 | $b = [MyTools.cache]::Get( [ref]$SFCMin, [ref]$SFCMax, [ref]$SFCFlags ) 44 | 45 | #typecast values so we can do some math with them 46 | $SFCMin = [long]$SFCMin 47 | $SFCMax = [long]$SFCMax 48 | $SFCFlags = [long]$SFCFlags 49 | 50 | write-output "Return values from GetSystemFileCacheSize are: " 51 | write-output "Function Result : $b" 52 | write-output " Min : $SFCMin" 53 | write-output (" Max : $SFCMax ( " + $SFCMax / 1024 / 1024 / 1024 + " GiB )") 54 | write-output " Flags : $SFCFlags" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A collection of useful PowerShell modules and scripts. 2 | # Modules 3 | The following modules are available in this repository. 4 | ### Write-ObjectToSQL 5 | [![Write-ObjectToSQL](https://img.shields.io/powershellgallery/v/Write-ObjectToSQL.svg?style=flat&label=Write-ObjectToSQL)](https://www.powershellgallery.com/packages/Write-ObjectToSQL/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/Write-ObjectToSQL?style=flat)](https://www.powershellgallery.com/packages/Write-ObjectToSQL/) 6 | 7 | This cmdlet accepts any type of object and will insert it into a database table, one row per object coming down the pipeline. If the table does not exist it will be created based on the properties of the first object in the pipeline. You can send pretty much anything to this cmdlet which makes it very useful when you want to quickly save the output from a script to a database. 8 | ### IniManager 9 | [![IniManager](https://img.shields.io/powershellgallery/v/IniManager.svg?style=flat&label=IniManager)](https://www.powershellgallery.com/packages/IniManager/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/IniManager?style=flat)](https://www.powershellgallery.com/packages/IniManager/) 10 | 11 | This module contains 7 cmdlets for managing ini files. 12 | ### WeatherForecast 13 | Contains a PowerShell class which is used for getting weather forecasts from SMHI (www.smhi.se). 14 | ### Edit-Command 15 | This module contains a cmdlet which can be used to edit other cmdlets in PowerShell ISE. 16 | ### Get-IMDBmovie 17 | This module contains a cmdlet for getting information about movies from IMDB. 18 | ### Get-ParamHelp 19 | This module contains a few cmdlets to make it easier to get help about parameter validation when writing advanced functions in PowerShell. 20 | # DSC Resources 21 | The following DSC resource is available in this repository. 22 | ### cIniFile 23 | This resource can be used for managing ini files with DSC. It uses the IniManager module and includes examples for both regular DSC configurations and composite resources. 24 | -------------------------------------------------------------------------------- /Scripts/Find-Hotfix/Find-Hotfix.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Checks for one or more patches on one or more servers. 4 | .DESCRIPTION 5 | Checks for one or more patches on one or more servers. The results show if each patch is installed or not on each server. 6 | The script uses the cmdlet Get-Hotfix so make sure you can run that towards the servers. 7 | Get-Hotfix does not return anything if no patch is found. This script works around that to show where the patches are not installed as well as where they are installed. 8 | 9 | Created by John Roos 10 | Email: john@roostech.se 11 | Web: http://blog.roostech.se 12 | .EXAMPLE 13 | .\Find-Hotfix.ps1 -Hotfix_FilePath 'c:\hotfixes.txt' -ComputerName_FilePath 'c:\servers.txt' 14 | .EXAMPLE 15 | .\Find-Hotfix.ps1 -Hotfix 'KB2991963', 'KB2923423' -ComputerName 'server01', 'server02' 16 | .EXAMPLE 17 | .\Find-Hotfix.ps1 -Hotfix 'KB2991963', 'KB2923423' -ComputerName_FilePath 'c:\servers.txt' 18 | .EXAMPLE 19 | .\Find-Hotfix.ps1 -Hotfix_FilePath 'c:\hotfixes.txt' -ComputerName 'server01', 'server02' 20 | .EXAMPLE 21 | .\Find-Hotfix.ps1 -Hotfix_FilePath 'c:\hotfixes.txt' -ComputerName_FilePath 'c:\servers.txt' | ConvertTo-Csv -NoTypeInformation | Out-File -PSPath test.csv 22 | 23 | This will export the result to a csv file. 24 | #> 25 | Param 26 | ( 27 | # Specify a path to the text file containing the list of hotfixes to search for. Must be one patch per row. 28 | [string] 29 | $Hotfix_FilePath, 30 | [string[]] 31 | # Specify the id of the patch to check (Example: KB2991963) 32 | $Hotfix, 33 | # Specify a path to the text file containing the list of servers to check. Must be one server per row. 34 | [string] 35 | $ComputerName_FilePath, 36 | # Specify the name of the server to check 37 | [string[]] 38 | $ComputerName 39 | ) 40 | 41 | if ($Hotfix_FilePath) { 42 | $find_hotfix = Get-Content -Path $Hotfix_FilePath 43 | }else{ 44 | $find_hotfix = $Hotfix 45 | 46 | } 47 | 48 | if ($ComputerName_FilePath){ 49 | $servers = Get-Content -Path $ComputerName_FilePath 50 | }else{ 51 | $servers = $ComputerName 52 | } 53 | 54 | # Could get error on this line in case some servers does not respond or bad permissions. 55 | $installedhf = Get-HotFix -Id $find_hotfix -ComputerName $servers 56 | 57 | foreach ($server in $servers){ 58 | if ($installedhf){ 59 | $temphf = $installedhf | Where-Object {$_.PSComputerName -eq $server} 60 | } 61 | foreach ($hf in $find_hotfix){ 62 | $verified_hotfix = New-Object psobject 63 | Add-Member -InputObject $verified_hotfix -MemberType 'NoteProperty' -Name "ComputerName" -Value $server 64 | Add-Member -InputObject $verified_hotfix -MemberType 'NoteProperty' -Name "Hotfix" -Value $hf 65 | 66 | if ($temphf){ 67 | if ($temphf.HotFixID -contains $hf) { 68 | Add-Member -InputObject $verified_hotfix -MemberType 'NoteProperty' -Name "Status" -Value 'Installed' 69 | }else{ 70 | Add-Member -InputObject $verified_hotfix -MemberType 'NoteProperty' -Name "Status" -Value 'Not installed' 71 | } 72 | } else { 73 | Add-Member -InputObject $verified_hotfix -MemberType 'NoteProperty' -Name "Status" -Value 'Not installed' 74 | } 75 | 76 | $verified_hotfix 77 | Remove-Variable -Name verified_hotfix 78 | } 79 | } -------------------------------------------------------------------------------- /Modules/IniManager/README.md: -------------------------------------------------------------------------------- 1 | # IniManager 2 | [![IniManager](https://img.shields.io/powershellgallery/v/IniManager.svg?style=flat&label=IniManager)](https://www.powershellgallery.com/packages/IniManager/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/IniManager?style=flat)](https://www.powershellgallery.com/packages/IniManager/) 3 | 4 | This module contains 7 cmdlets for managing ini files. 5 | * Get-Ini 6 | * New-Ini 7 | * Remove-IniKey 8 | * Rename-IniKey 9 | * Set-IniFromHash 10 | * Set-IniKey 11 | * Test-Ini 12 | 13 | A more detailed description can be found in this blog post: http://blog.roostech.se/2016/04/introducing-inimanager-module.html 14 | 15 | ## Get-Ini 16 | #### Description 17 | Reads an ini file and creates an object based on the content of the file. One property per key/value. Sections will be named with surrounding brackets and will contain a list of objects based on the keys within that section. 18 | Comments will be ignored. 19 | #### Example 20 | Get-Ini -Path "C:\config.ini" 21 | Opens the file config.ini and creates an object based on that file. 22 | #### Outputs 23 | Outputs an custom object of the type File.Ini 24 | 25 | ## Set-IniKey 26 | #### Description 27 | Sets a specific key to a value in a ini file. 28 | Comments will be ignored. 29 | Warning: Even comments in the target ini file will be removed! 30 | #### Example 31 | Set-IniKey -Path "C:\config.ini" -Key LoggingLevel -Value Debug -Section Logging -Encoding UTF8 32 | Opens the file config.ini and changes the key "LoggingLevel" in the [Logging] section of the file. The file will be saved with UTF8 encoding. 33 | #### Outputs 34 | Creates a ini file. Keeps the original content of the ini file and only replaces the value for the key matching the parameter Key 35 | 36 | ## Remove-IniKey 37 | #### Description 38 | Removes a key (entire line) in ini file 39 | Comments will be ignored. 40 | Warning: Even comments in the target ini file will be removed! 41 | #### Example 42 | Remove-IniKey -Path "c:\config.ini" -Key [system]Proxy -Encoding ASCII 43 | Opens the file config.ini and removes the key "Proxy" in the [system] section of the file. The file will be saved with ASCII encoding. 44 | #### Outputs 45 | Overwrites the original ini file 46 | 47 | ## Rename-IniKey 48 | #### Description 49 | Renames a key in ini file 50 | Comments will be ignored. 51 | Warning: Even comments in the target ini file will be removed! 52 | #### Example 53 | Rename-IniKey -Path c:\config.ini -Key Prixy -NewKey Proxy -Section system -Encoding UTF8 54 | Opens the file config.ini and renames the key "Prixy" to "Proxy" in the [system] section of the file. The file will be saved with UTF8 encoding. 55 | #### Outputs 56 | Overwrites the original ini file 57 | 58 | ## Set-IniFromHash 59 | #### Description 60 | Sets a number of keys and values in ini file based on a hash table. Sections are separated by naming them within brackets, like this: [section]key 61 | The keys will be added if they do not exist. 62 | Comments will be ignored. 63 | Warning: Even comments in the target ini file will be removed! 64 | #### Example 65 | Set-IniFromHash -Path c:\config.ini -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} -Encoding UTF8 66 | Opens the file config.ini and sets the key DebugLog to false and in the [settings] section sets the Hostname to localhost. 67 | #### Outputs 68 | Overwrites the original ini file 69 | 70 | ## New-Ini 71 | #### Description 72 | Creates an ini file based on a custom object of the type File.Ini 73 | Comments will be ignored. 74 | #### Example 75 | get-ini -Path "C:\config.ini" | new-ini -Path c:\config_new.ini -Encoding UTF8 76 | Opens the file config.ini and which creates a File.Ini object. The object is then piped to New-Ini which will create a new file based on that object. 77 | #### Outputs 78 | Creates a new ini file 79 | 80 | ## Test-Ini 81 | #### Description 82 | Reads the configuration from an ini file and compares the content with the provided hashtable. 83 | Comments will be ignored. 84 | #### Example 85 | Test-Ini -Path "C:\config.ini" -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} 86 | Opens the file config.ini and checks the values for 'DebugLog' (not in any section) and 'Hostname' (in section 'settubgs'). If the values are the same as the provided values the function will return True, otherwise it will return False. 87 | #### Outputs 88 | Boolean 89 | -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/data.json.tests.ps1: -------------------------------------------------------------------------------- 1 | # data.json path 2 | $jsonfilepath = Split-Path $MyInvocation.MyCommand.Path -Parent 3 | $jsonfilepath += '\data.json' 4 | 5 | # Unit tests for verifying data.json 6 | Describe "JSON data" { 7 | 8 | # Just a couple of quick tests before starting the proper tests. 9 | It "Json file can be read from disk" { 10 | if (Get-Content -Path $jsonfilepath) { 11 | $test = $true 12 | } else { 13 | $test = $false 14 | } 15 | $test | Should Be $true 16 | } 17 | 18 | It "Json file can be converted to PSobjects" { 19 | Get-Content -Path $jsonfilepath | ConvertFrom-Json | Should Not BeNullOrEmpty 20 | } 21 | 22 | # The actual tests of the content of the file 23 | 24 | $data = Get-Content -Path $jsonfilepath | ConvertFrom-Json 25 | $propsToHave = @{ 26 | 'Name' = [string] 27 | 'Type' = [string] 28 | 'Parent' = [string] 29 | 'Description' = [string] 30 | 'Examples' = [system.array] 31 | 'Links' = [system.array] 32 | } 33 | $propsNotToHave = @('ExamplesCount') 34 | $exampleprops = @{ 35 | Id = [int] 36 | Example = [string] 37 | Description = [string] 38 | } 39 | $linkprops = @{ 40 | Uri = [string] 41 | Description = [string] 42 | } 43 | foreach ($obj in $data.ParamHelp) { 44 | Context "$($obj.Name)" { 45 | 46 | # validate base properties 47 | foreach ($key in $propsToHave.Keys) { 48 | 49 | It "Has property $key" { 50 | $obj.psobject.Properties.Name -contains $key | Should Be $true 51 | } 52 | 53 | It "Property $key is $($propsToHave[$key])" { 54 | $obj.$key -is $propsToHave[$key] | Should Be $true 55 | } 56 | 57 | } 58 | 59 | # validate base properties that the object should not have (has slipped in before...) 60 | foreach ($prop in $propsNotToHave){ 61 | 62 | It "Does not have property $prop" { 63 | $obj.psobject.Properties.Name -notcontains $prop | Should Be $true 64 | } 65 | } 66 | 67 | # Validate Examples (if any) 68 | if ($obj.Examples.Length -gt 0){ 69 | $exID = 0 70 | foreach ($example in $obj.Examples) { 71 | $exID++ 72 | foreach ($prop in $exampleprops.Keys){ 73 | 74 | It "Example $exID has $prop property" { 75 | $example.psobject.Properties.Name -contains $prop | Should Be $true 76 | } 77 | 78 | It "Property $prop is $($exampleprops[$prop])" { 79 | $example.$prop | Should BeOfType $exampleprops[$prop] 80 | } 81 | 82 | } 83 | 84 | # Validate that no extra properties exist 85 | It "No additional Example properties" { 86 | $example.psobject.Properties.Name.Count | Should Be $exampleprops.Count 87 | } 88 | } 89 | } 90 | 91 | # Validate Links (if any) 92 | if ($obj.Links.Length -gt 0){ 93 | $linkID = 0 94 | foreach ($link in $obj.Links) { 95 | $linkID++ 96 | foreach ($prop in $linkprops.Keys){ 97 | 98 | It "Link $linkID has $prop property" { 99 | $link.psobject.Properties.Name -contains $prop | Should Be $true 100 | } 101 | 102 | It "Property $prop is $($linkprops[$prop])" { 103 | $link.$prop | Should BeOfType $linkprops[$prop] 104 | } 105 | 106 | } 107 | 108 | # Validate that no extra properties exist 109 | It "Has no additional Link properties" { 110 | $link.psobject.Properties.Name.Count | Should Be $linkprops.Count 111 | } 112 | } 113 | 114 | } 115 | 116 | # Validate that no extra properties exist 117 | It "Has no additional base properties" { 118 | $obj.psobject.Properties.Name.Count | Should Be $propsToHave.Count 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Modules/Get-IMDBmovie/Get-IMDBmovie.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | This scripts will get some basic information from IMDB about a movie by parcing the html code on the website. 4 | .DESCRIPTION 5 | This scripts will get some basic information from IMDB about a movie by parcing the html code on the website. 6 | 7 | Created by John Roos 8 | Email: john@roostech.se 9 | Web: http://blog.roostech.se 10 | .EXAMPLE 11 | Get-ImdbMovie -Title 'star trek' 12 | .EXAMPLE 13 | Get-ImdbMovie -Title 'star trek' -verbose 14 | #> 15 | function Get-IMDBMovie 16 | { 17 | [CmdletBinding()] 18 | Param 19 | ( 20 | # Enter the title of the movie you want to get information about 21 | [Parameter(Mandatory=$true, 22 | ValueFromPipelineByPropertyName=$true, 23 | Position=0)] 24 | [ValidateNotNullorEmpty()] 25 | [string]$Title 26 | ) 27 | 28 | Process 29 | { 30 | $searchTitle = $Title.Replace(' ','%20') 31 | 32 | Write-Verbose "Fetching search results" 33 | $moviesearch = Invoke-WebRequest "http://www.imdb.com/search/title?title=$searchTitle&title_type=feature" 34 | 35 | Write-Verbose "Moving html elements into variable" 36 | $titleclassarray = $moviesearch.AllElements | where Class -eq 'title' | select -First 1 37 | 38 | Write-Verbose "Checking if result contains movies" 39 | try { 40 | $titleclass = $titleclassarray[0] 41 | } 42 | catch { 43 | Write-Warning "No movie found matching that title http://www.imdb.com/search/title?title=$searchTitle&title_type=feature" 44 | break 45 | } 46 | 47 | if (!($titleclass)){ 48 | Write-Warning "No movie found matching that title http://www.imdb.com/search/title?title=$searchTitle&title_type=feature" 49 | break 50 | } 51 | 52 | Write-Verbose "Result contains movies." 53 | 54 | Write-Verbose "Parcing HTML for movie link." 55 | $regex = "<\s*a\s*[^>]*?href\s*=\s*[`"']*([^`"'>]+)[^>]*?>" 56 | $linksFound = [Regex]::Matches($titleclass.innerHTML, $regex, "IgnoreCase") 57 | 58 | $titlelink = New-Object System.Collections.ArrayList 59 | foreach($link in $linksFound) 60 | { 61 | $trimmedlink = $link.Groups[1].Value.Trim() 62 | if ($trimmedlink.Contains('/title/')) { 63 | [void] $titlelink.Add($trimmedlink) 64 | } 65 | } 66 | Write-Verbose "Movie link found." 67 | 68 | $movieURL = "http://www.imdb.com$($titlelink[0])" 69 | Write-Verbose "Fetching movie page." 70 | $moviehtml = Invoke-WebRequest $movieURL -Headers @{"Accept-Language"="en-US,en;"} 71 | Write-Verbose "Movie page fetched." 72 | 73 | $movie = New-Object -TypeName psobject 74 | 75 | Write-Verbose "Parcing for title." 76 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Title" -Value ($moviehtml.AllElements | where Class -eq 'itemprop' | select -First 1).innerText 77 | 78 | Write-Verbose "Parcing for directors." 79 | foreach ($line in ($moviehtml.AllElements | where Class -eq 'txt-block').InnerHTML -split "`n"){ 80 | if ($line -like '*Director:*'){ 81 | $line = $line.Replace('','') 82 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Directors" -Value $line.Remove(0,$line.LastIndexOf('>')+1) 83 | } 84 | } 85 | 86 | Write-Verbose "Parcing for year." 87 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Year" -Value (($moviehtml.AllElements | where Class -eq 'nobr' | select -First 1).innerText).Replace('(','').Replace(')','') 88 | 89 | Write-Verbose "Parcing for rating." 90 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Rating" -Value ($moviehtml.AllElements | where Class -eq 'titlePageSprite star-box-giga-star' | select -First 1).innerText 91 | 92 | Write-Verbose "Parcing for description." 93 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Description" -Value ($moviehtml.AllElements | where itemprop -eq 'description' | select -first 1).InnerText 94 | 95 | Write-Verbose "Parcing for writers." 96 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Writers" -Value ($moviehtml.AllElements | where itemprop -eq 'creator' | select -first 1).InnerText.Replace('Writers:','').Replace(' »','') 97 | 98 | Write-Verbose "Parcing for stars." 99 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Stars" -Value ($moviehtml.AllElements | where itemprop -eq 'actors').InnerText.Replace('Stars:','').Replace(' | See full cast and crew »','') 100 | 101 | Write-Verbose "Adding the link." 102 | Add-Member -InputObject $movie -MemberType 'NoteProperty' -Name "Link" -Value $movieURL 103 | 104 | Write-Verbose "Returning object." 105 | $movie 106 | } 107 | } -------------------------------------------------------------------------------- /Modules/WeatherForecast/README.md: -------------------------------------------------------------------------------- 1 | # WeatherForecast class 2 | This is a PowerShell class which is used for getting weather forecasts from SMHI (www.smhi.se). 3 | Why a class? Why not? :) 4 | 5 | ## Example 6 | Create a new object based on the class. 7 | ``` 8 | PS C:\> $forecastseries = [WeatherForeCast]::new() 9 | ``` 10 | Use method DownloadForeCast to refresh the object with current data from the API. 11 | ``` 12 | PS C:\> $forecastseries.DownloadForeCast() 13 | ``` 14 | Check the object. 15 | ``` 16 | PS C:\> $forecastseries 17 | 18 | Location : Stockholm, Sweden 19 | Longitude : 18.05 20 | Latitude : 59.3333 21 | StartDate : 2017-02-02 16:00:00 22 | EndDate : 2017-02-12 01:00:00 23 | ForeCast : {ForeCast, ForeCast, ForeCast, ForeCast...} 24 | ``` 25 | It`s a ten day forecast. The first day is on an hourly basis and the later days have lower frequency. 26 | Check the first two results. 27 | ``` 28 | PS C:\> $forecastseries.ForeCast | select -First 2 29 | 30 | ValidTime : 2017-02-02 16:00:00 31 | Temperature : 0.9 32 | RelativeHumidity : 81 33 | ThunderProbability : 0 34 | Visibility : 8.0 35 | AirPressure : 1016 36 | LowCloudCover : 8 37 | MediumCloudCover : 0 38 | HighCloudCover : 0 39 | TotalCloudCover : 8 40 | PrecipitationCategory : None 41 | MinimumPrecipitationIntensity : 0.0 42 | MaximumPrecipitationIntensity : 0.0 43 | WindDirection : 197 44 | WindGustVelocity : 6.1 45 | WindVelocity : 2.9 46 | WeatherSymbol : Overcast 47 | 48 | ValidTime : 2017-02-02 17:00:00 49 | Temperature : 0.7 50 | RelativeHumidity : 83 51 | ThunderProbability : 0 52 | Visibility : 9.0 53 | AirPressure : 1015 54 | LowCloudCover : 8 55 | MediumCloudCover : 0 56 | HighCloudCover : 2 57 | TotalCloudCover : 8 58 | PrecipitationCategory : None 59 | MinimumPrecipitationIntensity : 0.0 60 | MaximumPrecipitationIntensity : 0.0 61 | WindDirection : 203 62 | WindGustVelocity : 6.4 63 | WindVelocity : 2.9 64 | WeatherSymbol : Overcast 65 | ``` 66 | ## Inspect the object 67 | The WeatherForeCast object has a property called ForeCast which is an array of ForeCast objects. 68 | ``` 69 | PS C:\> $forecastseries | gm 70 | 71 | TypeName: WeatherForeCast 72 | 73 | Name MemberType Definition 74 | ---- ---------- ---------- 75 | AddForeCast Method void AddForeCast(ForeCast x) 76 | DownloadForeCast Method void DownloadForeCast() <-- Use to refresh the object with new data 77 | Equals Method bool Equals(System.Object obj) 78 | GetHashCode Method int GetHashCode() 79 | GetType Method type GetType() 80 | SetGeoLocation Method void SetGeoLocation() <-- Sets the geo location based on your IP number 81 | ToString Method string ToString() 82 | EndDate Property datetime EndDate {get;set;} <-- End date and time for the forecast 83 | ForeCast Property ForeCast[] ForeCast {get;set;} <-- Array with forecasts 84 | Latitude Property decimal Latitude {get;set;} 85 | Location Property string Location {get;set;} 86 | Longitude Property decimal Longitude {get;set;} 87 | StartDate Property datetime StartDate {get;set;} <-- Start date and time for the forecast 88 | ``` 89 | And the ForeCast object. 90 | ``` 91 | PS C:\> $forecastseries.ForeCast | gm 92 | 93 | TypeName: ForeCast 94 | 95 | Name MemberType Definition 96 | ---- ---------- ---------- 97 | Equals Method bool Equals(System.Object obj) 98 | GetHashCode Method int GetHashCode() 99 | GetType Method type GetType() 100 | SetPrecipitationCategory Method void SetPrecipitationCategory(int i) 101 | SetWeatherSymbol Method void SetWeatherSymbol(int i) 102 | ToString Method string ToString() 103 | AirPressure Property decimal AirPressure {get;set;} 104 | HighCloudCover Property int HighCloudCover {get;set;} 105 | LowCloudCover Property int LowCloudCover {get;set;} 106 | MaximumPrecipitationIntensity Property decimal MaximumPrecipitationIntensity {get;set;} 107 | MediumCloudCover Property int MediumCloudCover {get;set;} 108 | MinimumPrecipitationIntensity Property decimal MinimumPrecipitationIntensity {get;set;} 109 | PrecipitationCategory Property string PrecipitationCategory {get;set;} 110 | RelativeHumidity Property int RelativeHumidity {get;set;} 111 | Temperature Property decimal Temperature {get;set;} 112 | ThunderProbability Property int ThunderProbability {get;set;} 113 | TotalCloudCover Property int TotalCloudCover {get;set;} 114 | ValidTime Property datetime ValidTime {get;set;} 115 | Visibility Property decimal Visibility {get;set;} 116 | WeatherSymbol Property string WeatherSymbol {get;set;} 117 | WindDirection Property int WindDirection {get;set;} 118 | WindGustVelocity Property decimal WindGustVelocity {get;set;} 119 | WindVelocity Property decimal WindVelocity {get;set;} 120 | ``` 121 | -------------------------------------------------------------------------------- /Modules/WeatherForecast/WeatherForecast.ps1: -------------------------------------------------------------------------------- 1 | # Under construction 2 | # Needs a lot of error handling and other things 3 | 4 | class ForeCast { 5 | # Contains a forecast for a specific time and day 6 | 7 | # Date and time for the forecast 8 | [datetime]$ValidTime # date and time for the forecast 9 | # General 10 | [decimal]$Temperature # celsius 11 | [int]$RelativeHumidity # percent 12 | [int]$ThunderProbability # percent 13 | [decimal]$Visibility # kilometers 14 | [decimal]$AirPressure # hPa : Pressure reduced to medium sea level 15 | # Clouds 16 | [int]$LowCloudCover # 0-8 17 | [int]$MediumCloudCover # 0-8 18 | [int]$HighCloudCover # 0-8 : 8 = total coverage 19 | [int]$TotalCloudCover # 0-8 20 | # Precipitation 21 | [string]$PrecipitationCategory # see method SetPrecipitationCategory 22 | #[decimal]$SnowPrecipitationIntensity # millimeter 23 | [decimal]$MinimumPrecipitationIntensity # millimeter per hour 24 | [decimal]$MaximumPrecipitationIntensity # millimeter per hour 25 | # Wind 26 | [int]$WindDirection # degree 27 | [decimal]$WindGustVelocity # m/s 28 | [decimal]$WindVelocity # m/s 29 | # Weather symbol 30 | [string]$WeatherSymbol # see method SetWeatherSymbol 31 | 32 | 33 | 34 | [void] SetPrecipitationCategory ([int] $i) 35 | { 36 | [string]$cat = "" 37 | switch ($i) 38 | { 39 | 0 { $cat = 'None' } 40 | 1 { $cat = 'Snow' } 41 | 2 { $cat = 'Snow mixed with rain' } 42 | 3 { $cat = 'Rain' } 43 | 4 { $cat = 'Drizzle' } 44 | 5 { $cat = 'Freezing rain' } 45 | 6 { $cat = 'Freezing drizzle' } 46 | Default { Write-Error 'Unknown Precipitation Category - Parameter must be between 0 and 6.' } 47 | } 48 | $this.PrecipitationCategory = $cat 49 | } 50 | 51 | [void] SetWeatherSymbol ([int] $i) 52 | { 53 | [string]$cat = "" 54 | switch ($i) 55 | { 56 | 1 { $cat = 'Clear sky' } 57 | 2 { $cat = 'Nearly clear sky' } 58 | 3 { $cat = 'Variable cloudiness' } 59 | 4 { $cat = 'Halfclear sky' } 60 | 5 { $cat = 'Cloudy sky' } 61 | 6 { $cat = 'Overcast' } 62 | 7 { $cat = 'Fog' } 63 | 8 { $cat = 'Rain showers' } 64 | 9 { $cat = 'Thunderstorm' } 65 | 10 { $cat = 'Light sleet' } 66 | 11 { $cat = 'Snow showers' } 67 | 12 { $cat = 'Rain' } 68 | 13 { $cat = 'Thunder' } 69 | 14 { $cat = 'Sleet' } 70 | 15 { $cat = 'Snowfall' } 71 | Default { Write-Error 'Unknown Weather Symbol - Parameter must be between 1 and 15.' } 72 | } 73 | $this.WeatherSymbol = $cat 74 | } 75 | } 76 | 77 | class WeatherForeCast { 78 | #Contains an array of forecasts and metadata about the location 79 | 80 | # General variables 81 | [string]$Location 82 | [decimal]$Longitude 83 | [decimal]$Latitude 84 | [datetime]$StartDate 85 | [datetime]$EndDate 86 | 87 | # The forecast array 88 | [ForeCast[]]$ForeCast 89 | 90 | # Remove the default methods just for the hell of it 91 | # hidden [void] GetType () {} 92 | # hidden [void] GetHashCode () {} 93 | # hidden [void] Equals () {} 94 | # hidden [void] ToString () {} 95 | 96 | # Add forecast and update max and min dates 97 | [void] AddForeCast ( [ForeCast]$x ) { 98 | $this.ForeCast += $x 99 | if ($x.ValidTime -lt $this.StartDate -or $this.StartDate -eq [datetime]::MinValue){ 100 | $this.StartDate = $x.ValidTime 101 | } 102 | if ($x.ValidTime -gt $this.EndDate){ 103 | $this.EndDate = $x.ValidTime 104 | } 105 | } 106 | 107 | # This might be used later. Under construction. 108 | hidden [ForeCast] GetForeCast ([datetime]$x) { 109 | return $this.ForeCast 110 | } 111 | 112 | # Set the location from online rest api using the IP number 113 | [void] SetGeoLocation () { 114 | $raw = Invoke-RestMethod -Method Get -Uri 'http://ip-api.com/json' 115 | $this.Location = "$($raw.city), $($raw.country)" 116 | $this.Latitude = $raw.lat 117 | $this.Longitude = $raw.lon 118 | } 119 | 120 | # Downloads the forecast. Sets the location first if needed. 121 | [void] DownloadForeCast () { 122 | 123 | if (!$this.Latitude -or !$this.Longitude){ 124 | $this.SetGeoLocation() 125 | } 126 | 127 | $lat = $this.Latitude.ToString().Replace(',','.') 128 | $lon = $this.Longitude.ToString().Replace(',','.') 129 | 130 | #Old API 131 | #$uri = "http://opendata-download-metfcst.smhi.se/api/category/pmp1.5g/version/1/geopoint/lat/$lat/lon/$lon/data.json" 132 | 133 | #New API 134 | $uri = "http://opendata-download-metfcst.smhi.se/api/category/pmp2g/version/2/geotype/point/lon/$lon/lat/$lat/data.json" 135 | 136 | $RAWforecast = Invoke-RestMethod -Method Get -Uri $uri 137 | 138 | foreach ($current in $RAWforecast.timeseries) { 139 | $props = @{ 140 | ValidTime = $current.ValidTime 141 | HighCloudCover = ($current.parameters | Where-Object name -eq 'hcc_mean').values | Select-Object -First 1 142 | LowCloudCover = ($current.parameters | Where-Object name -eq 'lcc_mean').values | Select-Object -First 1 143 | MediumCloudCover = ($current.parameters | Where-Object name -eq 'mcc_mean').values | Select-Object -First 1 144 | AirPressure = ($current.parameters | Where-Object name -eq 'msl').values | Select-Object -First 1 145 | MinimumPrecipitationIntensity = ($current.parameters | Where-Object name -eq 'pmin').values | Select-Object -First 1 146 | MaximumPrecipitationIntensity = ($current.parameters | Where-Object name -eq 'pmax').values | Select-Object -First 1 147 | RelativeHumidity = ($current.parameters | Where-Object name -eq 'r').values | Select-Object -First 1 148 | Temperature = ($current.parameters | Where-Object name -eq 't').values | Select-Object -First 1 149 | TotalCloudCover = ($current.parameters | Where-Object name -eq 'tcc_mean').values | Select-Object -First 1 150 | ThunderProbability = ($current.parameters | Where-Object name -eq 'tstm').values | Select-Object -First 1 151 | Visibility = ($current.parameters | Where-Object name -eq 'vis').values | Select-Object -First 1 152 | WindDirection = ($current.parameters | Where-Object name -eq 'wd').values | Select-Object -First 1 153 | WindGustVelocity = ($current.parameters | Where-Object name -eq 'gust').values | Select-Object -First 1 154 | WindVelocity = ($current.parameters | Where-Object name -eq 'ws').values | Select-Object -First 1 155 | } 156 | $PrecipicationCategoryID = ($current.parameters | Where-Object name -eq 'pcat').values | Select-Object -First 1 157 | $WeatherSymbolID = ($current.parameters | Where-Object name -eq 'Wsymb').values | Select-Object -First 1 158 | 159 | $fc = New-Object -TypeName ForeCast -Property $props 160 | $fc.SetPrecipitationCategory($PrecipicationCategoryID) 161 | $fc.SetWeatherSymbol($WeatherSymbolID) 162 | 163 | $this.AddForeCast($fc) 164 | } 165 | } 166 | } 167 | 168 | 169 | break 170 | 171 | # Examples of how to use the class 172 | $forecastseries = [WeatherForeCast]::new() 173 | $forecastseries.DownloadForeCast() 174 | $forecastseries 175 | $forecastseries.ForeCast | select -First 10 176 | 177 | 178 | # Inspect the object 179 | $forecastseries | gm 180 | $forecastseries.ForeCast | gm 181 | 182 | 183 | # Just get the geo location 184 | $forecastseries.SetGeoLocation() 185 | $forecastseries 186 | 187 | # Coordinates for Stockholm which can be used while debugging (or to get crappy weather forecasts) 188 | # Latitud: 59.3420 Longitud: 18.0575 189 | -------------------------------------------------------------------------------- /Modules/Write-ObjectToSQL/README.md: -------------------------------------------------------------------------------- 1 | # Write-ObjectToSQL 2 | [![Write-ObjectToSQL](https://img.shields.io/powershellgallery/v/Write-ObjectToSQL.svg?style=flat&label=Write-ObjectToSQL)](https://www.powershellgallery.com/packages/Write-ObjectToSQL/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/Write-ObjectToSQL?style=flat)](https://www.powershellgallery.com/packages/Write-ObjectToSQL/) 3 | 4 | This Powershell cmdlet inserts properties of an object into a table. The table will be created if it doesnt exist. The cmdlet accepts object from the pipeline which makes this very useful when you want to easily save the output from a script in a database.
5 | More information can be found in this blog post: http://blog.roostech.se/2015/02/powershell-write-object-to-sql.html 6 | 7 | ## Description 8 | Writes an object into a database table. If the table does not exist it will be created based on the 9 | properties of the object. For every property of the object a column will be created. The data type for 10 | each column will be converted from .Net data types into SQL Server data types. 11 | 12 | Not all data types are supported. Unsupported data types will be ignored (but can be listed). 13 | If several objects are sent through the pipeline only the first object will be used for creating the 14 | template for the table. 15 | 16 | Make sure that all objects in the pipeline have the exact same properties (this is usually the case). 17 | While creating the table the script will also add two default columns. One called "id" which is a regular 18 | auto counter (integer which increases with 1 for every row) and another column called "inserted_at" which 19 | will have a default value of GetDate() which represents the timestamp for when the row was inserted. 20 | If a property is named the same as one of these default columns then a "x" will be added before the name 21 | of those columns to avoid duplication. (if propertyname=id, then propertyname=xid, etc.) 22 | 23 | Hashtables are handled slightly different. When using hashtables the script will simply use the keys as columns. 24 | 25 | Keep in mind that properties on the objects are used. Some objects, like strings, might only have a length 26 | property but what you really want to insert into the table is the value of the string. 27 | 28 | The following command would generate a table with one column called Length which would contain the length 29 | of the strings (probably not what you want): 30 | 31 | ```powershell 32 | 'oink','meo' | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName myTable 33 | ``` 34 | 35 | The following command is a better way to do it. Instead of piping the strings directly you should create 36 | custom objects or, as in this example, hash tables. This will generate a table with a column called 'text' 37 | which will contain the values 'oink' and 'meo': 38 | 39 | ```powershell 40 | @{'text'='oink'}, @{'text'='meo'} | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName myTable 41 | ``` 42 | 43 | Another thing to note is that this script will only take Property and NoteProperty into consideration. 44 | So for example ScriptProperty and ParameterizedProperty will be ignored. 45 | You can verify your objects with the Get-Member cmdlet and check the MemberType. 46 | 47 | Currently the script supports the following data types: 48 | 49 | * Int32 50 | * UInt32 51 | * Int16 52 | * UInt16 53 | * Int64 54 | * UInt64 55 | * long 56 | * int 57 | * Decimal 58 | * Single 59 | * Double 60 | * Byte 61 | * SByte 62 | * String 63 | * DateTime 64 | * TimeSpan 65 | * datetime 66 | * string 67 | * bool 68 | * Boolean 69 | 70 | 71 | ## Example 1 72 | ```powershell 73 | PS C:\> Get-Process | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName ProcessTable 74 | ``` 75 | 76 | Creates a table called ProcessTable (if it doesnt exist) based on the result from Get-Process. 77 | After the table is created all the objects will be inserted into the table. 78 | Some properties will be ignored because the data types are not supported. 79 | 80 | ## Example 2 81 | ```powershell 82 | PS C:\> Get-Process | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName ProcessTable -ShowIgnoredPropertiesOnly 83 | ``` 84 | ```text 85 | DependentServices 86 | ServiceHandle 87 | Status 88 | ServicesDependedOn 89 | Container 90 | RequiredServices 91 | ServiceType 92 | Site 93 | ``` 94 | 95 | This is useful for debugging. When using the parameter switch ShowIgnoredPropertiesOnly the cmdlet will not do anything in the database. 96 | Instead it will show which properties that will be ignored (unsupported data types). A complete run is still simulated so this command 97 | takes about the same time as if you would have inserted the objects into the table. 98 | 99 | ## Example 3 100 | ```powershell 101 | PS C:\> Get-Process | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName ProcessTable -ReportEveryXObject 20 102 | ``` 103 | ```text 104 | 2014-12-20 11:56:12 - Rows inserted: 20 (100 rows per second) 105 | 2014-12-20 11:56:12 - Rows inserted: 40 (250 rows per second) 106 | 2014-12-20 11:56:12 - Rows inserted: 60 (250 rows per second) 107 | Inserted 68 rows into ProcessTable in 0.41 seconds (165.85 rows per second) 108 | ``` 109 | This will insert objects into the database as usual with one difference: It will report back to the console every 20th object so that some kind of progress is shown. The cmdlet does not know how many objects are coming down the pipeline so this only shows what has happened, not how many objects that are left to process. 110 | ## Example 4 111 | ```powershell 112 | PS C:\> Get-Process | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName ProcessTable -TimeSpanType Milliseconds 113 | ``` 114 | ```text 115 | Inserted 68 rows into ProcessTable in 500.04 milliseconds (136 rows per second) 116 | ``` 117 | The TimeSpanType parameter can be used if you want to get the time it takes to run the script in a different kind of timespan type. 118 | There are four alternatives: Hours, Hinutes, Seconds and Milliseconds. 119 | ## Example 5 120 | ```powershell 121 | PS C:\> Get-WmiObject -ComputerName 'localhost' -Query "select * from win32_networkadapter where NetConnectionStatus = 2" | Write-ObjectToSQL -Server localhost\sqlexpress -Database MyDB -TableName wminetworkadapter 122 | ``` 123 | This is where the script becomes useful. This inserts information about the network adapters in localhost into a table called wminetworkadapter. 124 | Replace 'localhost' with a list of servers and you quickly have a simple inventory of network adapters on your servers. 125 | ## Example 6 126 | ```powershell 127 | PS C:\> Get-Process | Write-ObjectToSQL -ConnectionString "Provider=MySQL Provider; Data Source=Server01; User ID=MyUserName; Password=MyPassword; Initial Catalog=MyDatabaseName;" -TableName MyTableName 128 | ``` 129 | Experimental: 130 | This will insert the objects into a MySQL database. Running this multiple times will generate an error when attempting to create the table since the script is not able to check if it already exist (inserts will still work). 131 | When inserting objects into an existing table, use the parameter '-DoNotCreateTable' to avoid these errors if you are using a custom connection string. 132 | 133 | NOTE: As this is experimental it migth not work. I got it to work with MySQL as the example above shows, but I give no guarantees :) 134 | 135 | NOTE: It might work with other database engines as long as you have the proper connection string but I havent tested that. 136 | If you want to try it out, check www.connectionstrings.com to find the connection string you need. You might also need to install ODBC or OLEDB drivers. 137 | 138 | ## Inputs 139 | The script accepts an object as input from the pipeline. 140 | ## Outputs 141 | Outputs the result from the insert queries to the console together with some statistics on how long it took and the speed (rows per second). 142 | 143 | ## Future improvements 144 | 145 | * Add support for Timespan data type 146 | * Add SQL support for datetime data type 147 | * Add credential parameter 148 | * Add support for SQL Server accounts 149 | 150 | ## Fixed 151 | * Several new data types added 152 | * Now both creates the table and inserts the values 153 | * Fix empty property bug on subsequent objects in pipeline 154 | * Rewrote the property parsing completely 155 | * Moved repeated code to BEGIN for performance reasons 156 | * Generate SQL code only as a string with tablename validation 157 | * New parameter - Do not create table (fail if not exist) 158 | * New parameter - Only output "properties ignored" (easier to use when adding new data types) 159 | * New parameter to report progress at every X processed object - ReportEveryXObject 160 | * Add measure time functionality. Show time taken in seconds when showing how many rows were inserted. 161 | * Tidied up the code, removed obsolete comments 162 | * Fix proper verbose and warnings 163 | * Verified 'System.UInt32' compared to SQL Server data types 164 | * Make sure that ' is handled properly when inserting values 165 | * Add N'' for strings in inserts for SQL Server instead of just '' 166 | * Remove ' when creating table (replace "'" with "") 167 | * Add custom DB connection to avoid dependencies to other cmdlets 168 | * Fail the whole script if the first SELECT statement fails 169 | * Added several new data types (avoided BigInteger on purpose since it does not have proper max/min values) 170 | * Updated examples in the header 171 | * Add parameter TimespanType for selecting what to use when using Timespan. Options: Hours, Minutes, Seconds, Milliseconds 172 | * Add parameter sets to validate properly when using SQL Server compared to custom connection string 173 | * If any parameter is missing, fail the whole script (parameter grouping is needed) 174 | * Merge $modconnection and $connection since they are basically the same 175 | * Add OLEDB connection string functionality 176 | * Verify with MySQL and OLEDB 177 | * Fix so that properties can be handled even if NULL 178 | * Add switch TryAllPropertyTypes in case someone wants to insert more than just regular properties 179 | * Modified the loops generating database queries so that not that much code is repeating 180 | * Added support for hashtables. The scripts will now insert the hash values into columns named as the hash keys. 181 | * Rewrote the script to use a few functions instead. 182 | * Added better error handling if all properties are ignored. 183 | * Fixed a bug where insert statements based on a string property that had the same name as a reserved property was failing. 184 | 185 | # Links 186 | 187 | SQL Server data types: http://msdn.microsoft.com/en-us/library/ms187752.aspx 188 | 189 | C# data types: http://msdn.microsoft.com/en-us/library/ya5y69ds.aspx 190 | 191 | Built-In Types Table (C# Reference): http://msdn.microsoft.com/en-us/library/ya5y69ds.aspx 192 | 193 | VB data types: http://msdn.microsoft.com/en-us/library/47zceaw7.aspx 194 | 195 | VB.Net data types: http://www.tutorialspoint.com/vb.net/vb.net_data_types.htm 196 | 197 | -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Meta": { 4 | "SchemaVersion": "2.0", 5 | "RequiresModuleVersion": "1.1.0", 6 | "Revision": 42, 7 | "Source": "https://raw.githubusercontent.com/JohnRoos/PowerShell/master/Modules/Get-ParamHelp/data.json", 8 | "FileHistory": "https://github.com/JohnRoos/PowerShell/commits/master/Modules/Get-ParamHelp/data.json", 9 | "OnlineVersion": "http://blog.roostech.se/p/advancedfunctions.html", 10 | "Local": { 11 | "DownloadDate": "", 12 | "Revision": 0, 13 | "Modified": false 14 | } 15 | }, 16 | 17 | "ParamHelp": [ 18 | { 19 | "Name": "ValueFromPipeline", 20 | "Type": "Argument", 21 | "Parent": "Parameter", 22 | "Description": "The ValueFromPipeline argument indicates that the parameter accepts input from a pipeline object. Specify this argument if the function accepts the entire object, not just a property of the object.", 23 | "Examples": [ 24 | { 25 | "Id": 1, 26 | "Example": "[parameter(ValueFromPipeline=$true)]", 27 | "Description": "Enabled" 28 | }, 29 | { 30 | "Id": 2, 31 | "Example": "[parameter(ValueFromPipeline=$false)]", 32 | "Description": "Disabled (default)" 33 | }, 34 | { 35 | "Id": 3, 36 | "Example": "Function Get-ProcessID\r\n{\r\n Param\r\n (\r\n [Parameter(ValueFromPipeline=$true)] \r\n $Process\r\n )\r\n Get-Process -Name $Process | Select-Object -ExpandProperty id\r\n}", 37 | "Description": "Defines the parameter \"Process\" to accept value from the pipeline." 38 | } 39 | ], 40 | "Links": [ 41 | { 42 | "Uri": "https://msdn.microsoft.com/en-us/library/system.management.automation.parameterattribute.valuefrompipeline(v=vs.85).aspx", 43 | "Description": "ParameterAttribute.ValueFromPipeline Property (MSDN)" 44 | } 45 | ] 46 | }, 47 | { 48 | "Name": "ValueFromPipelineByPropertyName", 49 | "Type": "Argument", 50 | "Parent": "Parameter", 51 | "Description": "The valueFromPipelineByPropertyName argument indicates that the parameter accepts input from a property of a pipeline object. The object property must have the same name or alias as the parameter.", 52 | "Examples": [ 53 | { 54 | "Id": 1, 55 | "Example": "[parameter(ValueFromPipelineByPropertyName=$true)]", 56 | "Description": "Enabled" 57 | }, 58 | { 59 | "Id": 2, 60 | "Example": "[parameter(ValueFromPipelineByPropertyName=$false)]", 61 | "Description": "Disabled (default)" 62 | } 63 | ], 64 | "Links": [ 65 | { 66 | "Uri": "https://msdn.microsoft.com/en-us/library/system.management.automation.parameterattribute.valuefrompipelinebypropertyname(v=vs.85).aspx", 67 | "Description": "ParameterAttribute.ValueFromPipelineByPropertyName Property (MSDN)" 68 | } 69 | ] 70 | }, 71 | { 72 | "Name": "Parameter", 73 | "Type": "Attribute", 74 | "Parent": "", 75 | "Description": "The Parameter attribute is used to declare the attributes of function parameters.", 76 | "Examples": [ 77 | { 78 | "Id": 1, 79 | "Example": "[parameter(Argument=Value)]", 80 | "Description": "Declares validation argument for parameter." 81 | } 82 | ], 83 | "Links": [ 84 | { 85 | "Uri": "https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 86 | "Description": "Parameter Attribute Declaration (MSDN)" 87 | }, 88 | { 89 | "Uri": "https://msdn.microsoft.com/en-us/library/system.management.automation.parameterattribute(v=vs.85).aspx", 90 | "Description": "ParameterAttribute Class (MSDN)" 91 | } 92 | ] 93 | }, 94 | { 95 | "Name": "Mandatory", 96 | "Type": "Argument", 97 | "Parent": "Parameter", 98 | "Description": "Makes a parameter mandatory.", 99 | "Examples": [ 100 | { 101 | "Id": 1, 102 | "Example": "Param\r\n(\r\n [Parameter(Mandatory=$true)]\r\n $Param1\r\n)", 103 | "Description": "Sets the parameter Param1 to mandatory." 104 | } 105 | ], 106 | "Links": [ 107 | { 108 | "Uri": "https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 109 | "Description": "Parameter Attribute Declaration (MSDN)" 110 | }, 111 | { 112 | "Uri": "https://msdn.microsoft.com/en-us/library/system.management.automation.parameterattribute.mandatory(v=vs.85).aspx", 113 | "Description": "ParameterAttribute.Mandatory Property (MSDN)" 114 | } 115 | ] 116 | }, 117 | { 118 | "Name": "Position", 119 | "Type": "Argument", 120 | "Parent": "Parameter", 121 | "Description": "Defines which position the parameter has, if parameter names are not specified. If Position is not defined, the name has to be used.", 122 | "Examples": [ 123 | { 124 | "Id": 1, 125 | "Example": "[parameter(Position=0)]", 126 | "Description": "Sets the position to the first parameter if parameter name is omitted." 127 | } 128 | ], 129 | "Links": [ 130 | { 131 | "Uri": "https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 132 | "Description": "Parameter Attribute Declaration (MSDN)" 133 | } 134 | ] 135 | }, 136 | { 137 | "Name": "ParameterSetName", 138 | "Type": "Argument", 139 | "Parent": "Parameter", 140 | "Description": "Defines which parameter set a parameter belongs to. Multiple sets can be used. Parameters belonging to other sets will be filtered out if a set parameter is selected.", 141 | "Examples": [ 142 | { 143 | "Id": 1, 144 | "Example": "[parameter(ParameterSetName=\"MySetName\")]", 145 | "Description": "Sets the parameter set name to \"MySetName\"." 146 | } 147 | ], 148 | "Links": [ 149 | { 150 | "Uri": "https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 151 | "Description": "Parameter Attribute Declaration (MSDN)" 152 | } 153 | ] 154 | }, 155 | { 156 | "Name": "ValueFromRemainingArguments", 157 | "Type": "Argument", 158 | "Parent": "Parameter", 159 | "Description": "Sets the parameter to accept all remaining parameters which are not defined by other parameters.", 160 | "Examples": [ 161 | { 162 | "Id": 1, 163 | "Example": "param\r\n(\r\n [parameter(ValueFromRemainingArguments=$true)]\r\n [string[]]$Name\r\n)", 164 | "Description": "The the parameter Name accepts all remaining values passed to the function." 165 | } 166 | ], 167 | "Links": [ 168 | 169 | ] 170 | }, 171 | { 172 | "Name": "HelpMessage", 173 | "Type": "Argument", 174 | "Parent": "Parameter", 175 | "Description": "String with a description of the parameter which will prompt the user for mandatory parameters if not specified.", 176 | "Examples": [ 177 | { 178 | "Id": 1, 179 | "Example": "param\r\n(\r\n [parameter(mandatory=$true,\r\n helpmessage=\"Enter the name of the service\")]\r\n [string]$Service\r\n)", 180 | "Description": "This example defines a aelp message for the Service parameter." 181 | } 182 | ], 183 | "Links": [ 184 | 185 | ] 186 | }, 187 | { 188 | "Name": "Alias", 189 | "Type": "Attribute", 190 | "Parent": "", 191 | "Description": "Defines an alias for the parameter.", 192 | "Examples": [ 193 | { 194 | "Id": 1, 195 | "Example": "Param\r\n(\r\n [parameter()]\r\n [alias(\"computer\",\"name\")]\r\n [String]$Hostname\r\n)", 196 | "Description": "Adds computer and name as aliases to the ComputerName parameter." 197 | } 198 | ], 199 | "Links": [ 200 | 201 | ] 202 | }, 203 | { 204 | "Name": "AllowNull", 205 | "Type": "Validation attribute", 206 | "Parent": "", 207 | "Description": "Sets the parameter to accept $null to be submitted.", 208 | "Examples": [ 209 | { 210 | "Id": 1, 211 | "Example": "Param\r\n(\r\n [parameter(Mandatory=$true)]\r\n [AllowNull()]\r\n [String]$Service\r\n)", 212 | "Description": "Sets the mandatory parameter Service to accept a null value." 213 | } 214 | ], 215 | "Links": [ 216 | 217 | ] 218 | }, 219 | { 220 | "Name": "AllowEmptyString", 221 | "Type": "Validation attribute", 222 | "Parent": "", 223 | "Description": "Sets a mandatory parameter to accept an empty string.", 224 | "Examples": [ 225 | { 226 | "Id": 1, 227 | "Example": "Param\r\n(\r\n [parameter(Mandatory=$true)]\r\n [AllowEmptyString()]\r\n [String]$Service\r\n)", 228 | "Description": "Sets the mandatory parameter Service to accept an empty string." 229 | } 230 | ], 231 | "Links": [ 232 | 233 | ] 234 | }, 235 | { 236 | "Name": "AllowEmptyCollection", 237 | "Type": "Validation attribute", 238 | "Parent": "", 239 | "Description": "Sets a mandatory parameter to accept an empty collection (array).", 240 | "Examples": [ 241 | { 242 | "Id": 1, 243 | "Example": "Param\r\n(\r\n [parameter(Mandatory=$true)]\r\n [AllowEmptyCollection()]\r\n [String[]]$Service\r\n)", 244 | "Description": "Sets the mandatory parameter Service to accept an empty collection." 245 | } 246 | ], 247 | "Links": [ 248 | 249 | ] 250 | }, 251 | { 252 | "Name": "ValidateCount", 253 | "Type": "Validation attribute", 254 | "Parent": "", 255 | "Description": "Specifies the amount of values the parameter will accept.", 256 | "Examples": [ 257 | { 258 | "Id": 1, 259 | "Example": "Param\r\n(\r\n [parameter()]\r\n [ValidateCount(1,3)]\r\n [String[]]$Service\r\n)", 260 | "Description": "Sets the optional parameter Service to accept between one and three values." 261 | } 262 | ], 263 | "Links": [ 264 | 265 | ] 266 | }, 267 | { 268 | "Name": "ValidateLength", 269 | "Type": "Validation attribute", 270 | "Parent": "", 271 | "Description": "Specifies the amount of characters in the value which the parameter will accept.", 272 | "Examples": [ 273 | { 274 | "Id": 1, 275 | "Example": "Param\r\n(\r\n [parameter()]\r\n [ValidateLength(1,12)]\r\n [String]$Service\r\n)", 276 | "Description": "Sets the parameter Service to accept values which contain between one and twelve characters." 277 | } 278 | ], 279 | "Links": [ 280 | 281 | ] 282 | }, 283 | { 284 | "Name": "ValidatePattern", 285 | "Type": "Validation attribute", 286 | "Parent": "", 287 | "Description": "Regular expression which will be compared with the parameter value for validation.", 288 | "Examples": [ 289 | { 290 | "Id": 1, 291 | "Example": "param\r\n(\r\n [parameter()]\r\n [ValidatePattern(\u0027^[a-zA-Z0-9]+$\u0027)]\r\n [String]$Service\r\n)", 292 | "Description": "Sets the parameter Service to only accept alphanumeric characters." 293 | } 294 | ], 295 | "Links": [ 296 | 297 | ] 298 | }, 299 | { 300 | "Name": "ValidateRange", 301 | "Type": "Validation attribute", 302 | "Parent": "", 303 | "Description": "Specifies a numeric range which the parameter value can contain.", 304 | "Examples": [ 305 | { 306 | "Id": 1, 307 | "Example": "param\r\n(\r\n [parameter()]\r\n [ValidateRange(0,9)]\r\n [int]$Small\r\n)", 308 | "Description": "Sets the parameter Small to only accept numbers between 0 and 9." 309 | } 310 | ], 311 | "Links": [ 312 | 313 | ] 314 | }, 315 | { 316 | "Name": "ValidateScript", 317 | "Type": "Validation attribute", 318 | "Parent": "", 319 | "Description": "Specifies a script to verify the parameter value. The parameter value will be piped into the scriptblock. Validation will succeed if the script does not return $false or error.", 320 | "Examples": [ 321 | { 322 | "Id": 1, 323 | "Example": "param\r\n(\r\n [parameter()]\r\n [ValidateScript({$_ -like \u0027Z*\u0027})]\r\n [string]$Name\r\n)", 324 | "Description": "Sets the parameter Name to require a string starting with the letter Z." 325 | } 326 | ], 327 | "Links": [ 328 | 329 | ] 330 | }, 331 | { 332 | "Name": "ValidateSet", 333 | "Type": "Attribute", 334 | "Parent": "", 335 | "Description": "Specifies a set of approved parameter values. Only the values in the set will be accepted for the parameter or variable. If variables are used, they need to be constant. Since constant variables are accepted, enumerations can be used as well. A scriptblock can also be passed to the ValidateSet attribute, but this is rarely seen.", 336 | "Examples": [ 337 | { 338 | "Id": 1, 339 | "Example": "Param\r\n(\r\n [ValidateSet(\"Cat\", \"Dog\", \"Fish\")]\r\n [string]$Pet\r\n)", 340 | "Description": "The Pet parameter will only accept the strings Cat, Dog or Fish." 341 | }, 342 | { 343 | "Id": 2, 344 | "Example": "Param\r\n(\r\n [ValidateSet(1,2,3,4,5)]\r\n [int]$Number\r\n)", 345 | "Description": "This parameter will only accept numbers in the range 1-5." 346 | }, 347 | { 348 | "Id": 3, 349 | "Example": "Param\r\n(\r\n [DayOfWeek]$Weekday\r\n)", 350 | "Description": "Instead of using ValidateSet its possible to use enumerations to accomplish a similar result. In this example [DayOfWeek] is an enum which contains the names of the days of the week." 351 | }, 352 | { 353 | "Id": 4, 354 | "Example": "$ValidateServiceType = @\"\r\n public enum ValidateService\r\n {\r\n `n$(foreach ($name in (Get-Service -Name a*).Name){\"`t$name`,\"})`n\r\n }\r\n\"@\r\nAdd-Type -TypeDefinition $ValidateServiceType\r\n\r\nfunction Get-ServiceName\r\n{\r\n [CmdletBinding()]\r\n Param\r\n (\r\n [ValidateService]$Name\r\n )\r\n return $Name\r\n}", 355 | "Description": "Since enumerations are allowed and can accomplish similar results as ValidateSet, you can create your own enumeration to validate with. This example validates the Name parameter with service names starting with the letter \u0027a\u0027." 356 | } 357 | ], 358 | "Links": [ 359 | 360 | ] 361 | }, 362 | { 363 | "Name": "ValidateNotNull", 364 | "Type": "Validation attribute", 365 | "Parent": "", 366 | "Description": "Specifies that the parameter value cannot contain $null.", 367 | "Examples": [ 368 | { 369 | "Id": 1, 370 | "Example": "param\r\n(\r\n [parameter()]\r\n [ValidateNotNull()]\r\n $Name\r\n)", 371 | "Description": "The value for the parameter Name cannot be null." 372 | } 373 | ], 374 | "Links": [ 375 | 376 | ] 377 | }, 378 | { 379 | "Name": "ValidateNotNullOrEmpty", 380 | "Type": "Validation attribute", 381 | "Parent": "", 382 | "Description": "Specifies that the parameter value cannot contain $null or be an empty string.", 383 | "Examples": [ 384 | { 385 | "Id": 1, 386 | "Example": "param\r\n(\r\n [parameter()]\r\n [ValidateNotNullOrEmpty()]\r\n [string]$Name\r\n)", 387 | "Description": "The value for the parameter Name cannot be null or empty." 388 | } 389 | ], 390 | "Links": [ 391 | 392 | ] 393 | } 394 | ] 395 | } 396 | ] 397 | -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/Get-ParamHelp.ps1: -------------------------------------------------------------------------------- 1 | 2 | #region functions 3 | function Get-ParamHelp { 4 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 5 | [OutputType([String],[PSObject])] 6 | param ( 7 | [Parameter(Mandatory=$false, 8 | ValueFromPipelineByPropertyName=$true, 9 | Position=0)] 10 | [String]$Name, 11 | 12 | [Parameter(Mandatory=$false, 13 | Position=5)] 14 | [switch]$Raw, 15 | [switch]$Online 16 | ) 17 | <# 18 | DynamicParam { 19 | $attributes1 = New-Object System.Management.Automation.ParameterAttribute 20 | $attributes1.ValueFromPipelineByPropertyName = $true 21 | $attributes1.Position = 0 22 | $attributes1.HelpMessage = 'Enter the name of the parameter validation argument or attribute' 23 | 24 | $attributes2 = New-Object System.Management.Automation.ParameterAttribute 25 | $attributes2.Position = 1 26 | $attributes2.HelpMessage = 'Use this switch parameter to get the raw object instead of a help message.' 27 | 28 | $attributeCollection1 = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] 29 | $attributeCollection1.Add($attributes1) 30 | 31 | $attributeCollection2 = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] 32 | $attributeCollection2.Add($attributes2) 33 | 34 | $_Values1 = (GetJsonData).Name 35 | $ValidateSet1 = new-object System.Management.Automation.ValidateSetAttribute($_Values1) 36 | $attributeCollection1.Add($ValidateSet1) 37 | 38 | $dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Name", [string], $attributeCollection1) 39 | $dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Raw", [switch], $attributeCollection2) 40 | 41 | $paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary 42 | $paramDictionary.Add("Name", $dynParam1) 43 | $paramDictionary.Add("Raw", $dynParam2) 44 | 45 | return $paramDictionary 46 | } 47 | #> 48 | 49 | 50 | 51 | Begin { 52 | $allobjects = GetJsonData 53 | } 54 | 55 | Process { 56 | # $Name = $PSBoundParameters['Name'] 57 | $jsonobjects = @() 58 | foreach ($object in $allobjects) { 59 | if ( $object.Name -like $Name -or !$Name) { 60 | $jsonobjects += $object 61 | } 62 | } 63 | 64 | if (!$jsonobjects){ 65 | Throw "Could not find help content searching for '$Name'" 66 | return 67 | # } elseif ($jsonobjects.count -gt 1 -or $PSBoundParameters['Raw']) { 68 | } elseif ($jsonobjects.count -gt 1 -or $Raw) { 69 | return $jsonobjects 70 | } 71 | 72 | if ($Online) { 73 | Start-Process -FilePath "http://blog.roostech.se/p/advancedfunctions.html#$($jsonobjects[0].Name)" 74 | return 75 | } 76 | 77 | $jsonobject = $jsonobjects[0] 78 | $completestring = "" 79 | $namestring = $jsonobject.Name + " (" + $jsonobject.Type + ")" 80 | $namestring = "NAME`n" + (GetPaddedString -Padding 4 -String $namestring) 81 | $descstring = "DESCRIPTION`n" + (GetPaddedString -Padding 4 -String ($jsonobject.Description)) 82 | 83 | $examplestring = "" 84 | for ($i = 0; $i -lt $jsonobject.Examples.Count; $i++) { 85 | $examplestring += (GetPaddedString -Padding 4 -String ("-------------------------- EXAMPLE " + ($jsonobject.Examples[$i].Id) + " --------------------------")) + "`n`n" 86 | $examplestring += (GetPaddedString -Padding 4 -String ($jsonobject.Examples[$i].Description)) + "`n`n" 87 | $examplestring += (GetPaddedString -Padding 8 -String ($jsonobject.Examples[$i].Example)) + "`n`n`n" 88 | } 89 | 90 | $linkstring = "RELATED LINKS`n" 91 | for ($i = 0; $i -lt $jsonobject.Links.Count; $i++) { 92 | $linkstring += (GetPaddedString -Padding 4 -String ($jsonobject.Links[$i].Description + ":")) + "`n" 93 | $linkstring += " " + ($jsonobject.Links[$i].Uri + "`n`n") 94 | } 95 | 96 | $completestring += "`n`n$namestring`n`n$descstring`n`n`n$examplestring$linkstring" 97 | return $completestring 98 | } 99 | } 100 | function Add-ParamHelp { 101 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 102 | param ( 103 | # Enter the name of the parameter validation argument or attribute 104 | [Parameter(Mandatory=$true, 105 | ValueFromPipelineByPropertyName=$true, 106 | Position=0)] 107 | [ValidateNotNullOrEmpty()] 108 | [string]$Name, 109 | [string]$Type, 110 | [string]$Parent, 111 | [string]$Description 112 | ) 113 | 114 | Begin { 115 | $allobjects = GetJsonData 116 | } 117 | 118 | Process 119 | { 120 | if ($allobjects.Name -contains $Name) { 121 | Write-Error "Name already exist" 122 | return 123 | } 124 | 125 | $examples = New-Object -TypeName System.Collections.ArrayList 126 | $links = New-Object -TypeName System.Collections.ArrayList 127 | 128 | $props = [ordered]@{ 129 | Name = $Name 130 | Type = $Type 131 | Parent = $Parent 132 | Description = $Description 133 | Examples = $examples 134 | Links = $links 135 | } 136 | $obj = New-Object -TypeName psobject -Property $props 137 | $allobjects += $obj 138 | SaveJsonData -data $allobjects 139 | } 140 | 141 | } 142 | #add ShouldProcess 143 | function Set-ParamHelp { 144 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 145 | param ( 146 | # Enter the name of the parameter validation argument or attribute 147 | [Parameter(Mandatory=$true, 148 | ValueFromPipelineByPropertyName=$true, 149 | Position=0)] 150 | [ValidateNotNullOrEmpty()] 151 | [string]$Name, 152 | [string]$NewName, 153 | [string]$Type, 154 | [string]$Parent, 155 | [string]$Description 156 | ) 157 | 158 | Begin { 159 | $allobjects = GetJsonData 160 | } 161 | Process { 162 | 163 | if (-not (ValidateOneMatch -Name $Name -allobjects $allobjects)) { 164 | return 165 | } 166 | # Only one match, continue 167 | 168 | for ($i = 0; $i -lt $allobjects.Count; $i++) { 169 | if ($allobjects[$i].Name -eq $Name) { 170 | # Found object. Time to update. 171 | if ($NewName){ 172 | $allobjects[$i].Name = $NewName 173 | } 174 | if ($Type){ 175 | $allobjects[$i].Type = $Type 176 | } 177 | if ($Parent){ 178 | $allobjects[$i].Parent = $Parent 179 | } 180 | if ($Description){ 181 | $allobjects[$i].Description = $Description 182 | } 183 | } 184 | } 185 | 186 | SaveJsonData -data $allobjects 187 | 188 | } 189 | } 190 | function Add-ParamHelpExample { 191 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 192 | param ( 193 | # Enter the name of the parameter validation argument or attribute 194 | [Parameter(Mandatory=$true, 195 | ValueFromPipelineByPropertyName=$true, 196 | Position=0)] 197 | [ValidateNotNullOrEmpty()] 198 | [string]$Name, 199 | 200 | # Enter the example code 201 | [Parameter(Mandatory=$true, 202 | ValueFromPipelineByPropertyName=$true, 203 | Position=1)] 204 | [ValidateNotNullOrEmpty()] 205 | [string]$Example, 206 | 207 | # Enter the description of the example 208 | [Parameter(Mandatory=$true, 209 | ValueFromPipelineByPropertyName=$true, 210 | Position=3)] 211 | [ValidateNotNullOrEmpty()] 212 | [string]$ExampleDescription 213 | ) 214 | 215 | Begin { 216 | $allobjects = GetJsonData 217 | } 218 | Process { 219 | ValidateOneMatch -Name $Name -allobjects $allobjects | Out-Null 220 | <# 221 | if (-not (ValidateOneMatch -Name $Name -allobjects $allobjects)) { 222 | Throw "Name must be unique" 223 | } 224 | #> 225 | # Only one match, continue 226 | 227 | for ($i = 0; $i -lt $allobjects.Count; $i++) { 228 | if ($allobjects[$i].Name -eq $Name) { 229 | # Found object. Time to update. 230 | $x = $allobjects[$i].Examples.Count 231 | $allobjects[$i].Examples += [ordered]@{ 232 | Id = $x+1 233 | Example = $Example 234 | Description = $ExampleDescription 235 | } 236 | 237 | } 238 | } 239 | SaveJsonData -data $allobjects 240 | } 241 | } 242 | #add ShouldProcess 243 | function Set-ParamHelpExample { 244 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 245 | param ( 246 | # Enter the name of the parameter validation argument or attribute 247 | [Parameter(Mandatory=$true, 248 | ValueFromPipelineByPropertyName=$true, 249 | Position=0)] 250 | [ValidateNotNullOrEmpty()] 251 | [string]$Name, 252 | 253 | # Enter the id of the example 254 | [Parameter(Mandatory=$true, 255 | ValueFromPipelineByPropertyName=$true, 256 | Position=1)] 257 | [int]$ExampleId, 258 | 259 | # Enter the example code 260 | [Parameter(Mandatory=$false, 261 | ValueFromPipelineByPropertyName=$true, 262 | Position=2)] 263 | [ValidateNotNullOrEmpty()] 264 | [string]$ExampleCode, 265 | 266 | # Enter the description of the example 267 | [Parameter(Mandatory=$false, 268 | ValueFromPipelineByPropertyName=$true, 269 | Position=3)] 270 | [ValidateNotNullOrEmpty()] 271 | [string]$ExampleDescription 272 | ) 273 | 274 | Begin { 275 | $allobjects = GetJsonData 276 | } 277 | Process { 278 | ValidateOneMatch -Name $Name -allobjects $allobjects | Out-Null 279 | for ($i = 0; $i -lt $allobjects.Count; $i++) { 280 | if ($allobjects[$i].Name -eq $Name) { 281 | # Found object. Time to update. 282 | # Add error handling if the example does not exist 283 | for ($e = 0; $e -lt $allobjects[$i].Examples.Count; $e++) { 284 | if (($allobjects[$i].Examples[$e].id) -eq $ExampleId){ 285 | if ($ExampleCode){ 286 | $allobjects[$i].Examples[$e].Example = $ExampleCode 287 | } 288 | if ($ExampleDescription){ 289 | $allobjects[$i].Examples[$e].Description = $ExampleDescription 290 | } 291 | } 292 | } 293 | } 294 | } 295 | SaveJsonData -data $allobjects 296 | } 297 | } 298 | function Remove-ParamHelpExample { 299 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/', 300 | SupportsShouldProcess=$true, 301 | ConfirmImpact="Low")] 302 | param ( 303 | # Enter the name of the parameter validation argument or attribute 304 | [Parameter(Mandatory=$true, 305 | ValueFromPipelineByPropertyName=$true, 306 | Position=0)] 307 | [ValidateNotNullOrEmpty()] 308 | [string]$Name, 309 | 310 | # Enter the id of the example 311 | [Parameter(Mandatory=$true, 312 | ValueFromPipelineByPropertyName=$true, 313 | Position=1)] 314 | [int[]]$ExampleId 315 | ) 316 | 317 | Begin { 318 | $allobjects = GetJsonData 319 | } 320 | 321 | Process { 322 | Write-Verbose "Validating one match" 323 | ValidateOneMatch -Name $Name -allobjects $allobjects | Out-Null 324 | Write-Verbose "Searching for help content" 325 | for ($i = 0; $i -lt $allobjects.Count; $i++) { 326 | if ($allobjects[$i].Name -eq $Name) { 327 | Write-Verbose "Found base object. Looking for example id." 328 | $NewExampleArray = @() 329 | foreach ($example in $allobjects[$i].Examples){ 330 | if ($ExampleId -notcontains $example.id){ 331 | Write-Verbose "Not this one" 332 | $NewExampleArray += $example 333 | } else { 334 | Write-Verbose "Found it" 335 | $ExampleId = $ExampleId.Where({$_ -ne $example.id}) 336 | } 337 | } 338 | Write-Verbose "Rebuilding object without the removed example" 339 | $allobjects[$i].Examples = $NewExampleArray 340 | } 341 | } 342 | if ($ExampleId.count -eq 0) { 343 | Write-Verbose "Saving to disk" 344 | SaveJsonData -data $allobjects 345 | } else { 346 | Throw "Could not find example $($ExampleId -join ', ')." 347 | } 348 | } 349 | } 350 | #endregion 351 | 352 | #region Todo 353 | function Add-Link {} 354 | #add ShouldProcess 355 | function Set-Link {} 356 | #add ShouldProcess 357 | function Remove-Link {} 358 | #add ShouldProcess 359 | function Remove-Example {} 360 | #add ShouldProcess 361 | Function Update-ParamHelp { 362 | [cmdletbinding()] 363 | param () 364 | 365 | # Get local meta data 366 | $LocalMeta = GetJsonData -Meta 367 | $modulepath = $PSScriptRoot 368 | $localRevision = $LocalMeta.Meta.Revision - $LocalMeta.Meta.Local.Revision 369 | 370 | # Checking for local changes 371 | if ($LocalMeta.Meta.Local.Modified) { 372 | Write-Warning "ParamHelp data has been locally modified. Use the Force parameter to overwrite all locally saved ParamHelp data." 373 | return 374 | } 375 | 376 | # Download update 377 | Write-Verbose 'Checking for update' 378 | $DownloadedUpdate = DownloadUpdate -Uri $LocalMeta.Meta.Source -ErrorAction Stop 379 | 380 | # compare revision with current, if online version is newer: continue 381 | if ($localRevision -gt $DownloadedUpdate.Meta.Revision) { 382 | Write-Error "Local revision is newer than the online version. Aborting update." 383 | return 384 | } elseif ($localRevision -eq $DownloadedUpdate.Meta.Revision) { 385 | Write-Verbose "Local data is up to date. Aborting update." 386 | return 387 | } 388 | 389 | # compare module version requirement 390 | if ($DownloadedUpdate.Meta.RequiresModuleVersion -gt $MyInvocation.MyCommand.Module.Version) { 391 | Write-Error -Message "This update requires module version $($DownloadedUpdate.Meta.RequiresModuleVersion). Please update ParamHelp module before updating help data. To update use the following command: Update-Module Get-ParamHelp" 392 | } 393 | 394 | # backup local data 395 | 396 | Write-Verbose 'Backing up local data' 397 | $timestamp = Get-Date -Format o | foreach {$_ -replace ":", "."} 398 | Copy-Item -Path "$modulepath\data.json" -Destination "$modulepath\data_$timestamp.json" 399 | 400 | # set local meta 401 | $DownloadedUpdate.Meta.Local.DownloadDate = Get-Date -Format o 402 | $DownloadedUpdate.Meta.Local.Modified = $false 403 | $DownloadedUpdate.Meta.Local.Revision = 0 404 | 405 | # save file 406 | $DownloadedUpdate.PSObject.TypeNames.Insert(0,'ParameterHelp') 407 | SaveJsonData -data $DownloadedUpdate -KeepRevision 408 | Write-Output "ParamHelp data updated. Whats new: $($DownloadedUpdate.Meta.FileHistory)" 409 | 410 | } 411 | 412 | 413 | #endregion 414 | 415 | #region helper-functions 416 | 417 | function DownloadUpdate { 418 | [CmdletBinding(HelpUri = 'http://blog.roostech.se/')] 419 | param ( 420 | [Parameter(Mandatory=$true)] 421 | [ValidateNotNull()] 422 | [Uri]$Uri 423 | ) 424 | 425 | Process 426 | { 427 | $webrequest = Invoke-WebRequest -Uri $Uri -UseBasicParsing -ErrorAction Stop -Verbose:$false 428 | $converted = ConvertFrom-Json -InputObject $webrequest -ErrorAction Stop -Verbose:$false 429 | 430 | # basic verification 431 | if ([string]::IsNullOrEmpty($converted.Meta.RequiresModuleVersion)) 432 | { 433 | Write-Error "Cannot use downloaded data. Missing RequiresModuleVersion in Meta" 434 | } 435 | if (-not [int]::Parse($converted.Meta.Revision)) 436 | { 437 | Write-Error "Cannot use downloaded data. Missing Revision in Meta" 438 | } 439 | if ([string]::IsNullOrEmpty($converted.ParamHelp[0].Name)) 440 | { 441 | Write-Error "Cannot use downloaded data. ParamHelp data is in wrong format. (missing name)" 442 | } 443 | if ([string]::IsNullOrEmpty($converted.ParamHelp[0].description)) 444 | { 445 | Write-Error "Cannot use downloaded data. ParamHelp data is in wrong format. (missing description)" 446 | } 447 | 448 | # return entire object, including meta 449 | $converted 450 | } 451 | } 452 | 453 | function GetPaddedString { 454 | param ([int]$Padding, [string]$String) 455 | $ConsoleWidth = (Get-Host).UI.RawUI.BufferSize.Width 456 | if ($Padding -le 0){ 457 | $Padding = 1 458 | } 459 | 460 | $outputString = "" 461 | $stringlines = $String.ToString() -split "`r`n|`r|`n" 462 | foreach ($line in $stringlines){ 463 | if ($outputString.Length -gt 1) { 464 | $outputString += "`n" 465 | } 466 | $stringArray = $line.Split(' ') 467 | $lineString = " " * ($Padding-1) 468 | for ($i = 0; $i -lt $stringArray.count; $i++) { 469 | if (($lineString + " " + $stringArray[$i]).Length -le $ConsoleWidth-1) { 470 | $lineString += " " + $stringArray[$i] 471 | } else { 472 | $outputString += $linestring 473 | $outputString += "`n" 474 | $linestring = (" " * $Padding) + $stringArray[$i] 475 | } 476 | 477 | if ($i -eq ($stringArray.count - 1)) { 478 | $outputString += $linestring 479 | } 480 | } 481 | } 482 | return $outputString 483 | } 484 | function ValidateOneMatch { 485 | param ( 486 | $Name, 487 | $allobjects 488 | ) 489 | $jsonobjects = @() 490 | foreach ($object in $allobjects.Name) { 491 | if ( $object -eq $Name) { 492 | $jsonobjects += $object 493 | } 494 | } 495 | 496 | if (!$jsonobjects){ 497 | Throw "Could not find parameter help content with that name." 498 | } elseif ($jsonobjects.count -gt 1) { 499 | Throw "Found multiple matches. Thats wierd..." 500 | } else { 501 | return $true 502 | } 503 | } 504 | function GetJsonData { 505 | param ( 506 | [string]$Path, 507 | [switch]$Meta, 508 | [psobject]$Data 509 | ) 510 | 511 | if ([string]::IsNullOrEmpty($Path)) { 512 | $modulepath = Split-Path -Path $MyInvocation.MyCommand.Module.Path -Parent 513 | $Path = "$modulepath\data.json" 514 | } 515 | 516 | if (-not (Test-Path -Path $Path)) { 517 | Throw "Path not found: $Path" 518 | } 519 | if ($Data) { 520 | $allobjects = $Data 521 | } else { 522 | $allobjects = Get-Content -Path $Path -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop 523 | } 524 | if ($Meta) { 525 | if (($allobjects | Get-Member -MemberType NoteProperty).Name -notcontains 'Meta') { 526 | Throw "Json file is not valid. Metadata is missing." 527 | } 528 | } 529 | $jsonobjects = @() 530 | foreach ($object in $allobjects.ParamHelp) { 531 | 532 | $props = $object | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name 533 | if (-not ($props -contains 'Name' -and $props -contains 'Description')) { 534 | Throw "Json file is not valid" 535 | } 536 | $object.PSObject.TypeNames.Insert(0,'ParameterHelp') 537 | #if ($object.Examples) { 538 | Add-Member -InputObject $object -MemberType ScriptProperty -Name ExamplesCount -Value { 539 | if (($this | Get-Member -MemberType NoteProperty).Name.Contains('Examples')) { 540 | return $this.Examples.Count 541 | } else { 542 | return 0 543 | } 544 | } 545 | #} else { 546 | #Add-Member -InputObject $object -MemberType ScriptProperty -Name ExamplesCount -Value 0 547 | #} 548 | $jsonobjects += $object 549 | } 550 | Update-TypeData -TypeName ParameterHelp -DefaultDisplayPropertySet Name, Type, ExamplesCount -Force -WhatIf:$false 551 | if ($Meta) { 552 | $completeObject = New-Object -TypeName psobject -Property @{ 553 | Meta = $allobjects.Meta 554 | ParamHelp = $jsonobjects 555 | } 556 | return $completeObject 557 | } else { 558 | return $jsonobjects 559 | } 560 | 561 | 562 | } 563 | # add default path, same as GetJsonData 564 | function SaveJsonData { 565 | param ( 566 | [pscustomobject[]]$data, 567 | [pscustomobject]$meta, 568 | [string]$Path, 569 | [switch]$KeepRevision 570 | ) 571 | 572 | # move to parameter script validation? 573 | foreach ($object in $data) { 574 | if ($object.psobject.TypeNames -notcontains 'ParameterHelp'){ 575 | Throw "Data parameter only accepts one or more ParameterHelp objects" 576 | } 577 | } 578 | 579 | if ([string]::IsNullOrEmpty($Path)) { 580 | #$modulepath = Split-Path -Path $MyInvocation.MyCommand.Module.Path -Parent 581 | $Path = "$PSScriptRoot\data.json" 582 | } 583 | 584 | # move to parameter script validation? 585 | if (-not (Test-Path -Path (Split-Path -Path $Path -Parent))) { 586 | Throw "Directory not found" 587 | } 588 | 589 | $originalJson = getJsonData -Path $Path -Meta 590 | $originalJson.ParamHelp = $data 591 | 592 | # update local meta 593 | if (!$KeepRevision) { 594 | $originalJson.Meta.Revision += 1 595 | $originalJson.Meta.Local.Revision += 1 596 | $originalJson.Meta.Local.Modified = $true 597 | } 598 | 599 | try { 600 | $originalJson.ParamHelp = $originalJson.ParamHelp | Sort-Object -Property Name | Select-Object -Property * -ExcludeProperty ExamplesCount 601 | ConvertTo-Json -InputObject $originalJson -Depth 4 -ErrorAction Stop | Out-File -FilePath $Path -ErrorAction Stop 602 | } catch { 603 | Write-Error "Unable to save json file." 604 | } 605 | } 606 | #endregion -------------------------------------------------------------------------------- /Modules/Get-ParamHelp/Get-ParamHelp.tests.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'GetJsonData' } 3 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'SaveJsonData' } 4 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = '*JsonData' } 5 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Get-ParamHelp' } 6 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Set-ParamHelp' } 7 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1' } 8 | 9 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -Tag New 10 | #> 11 | 12 | 13 | 14 | # data.json path 15 | $basepath = Split-Path $MyInvocation.MyCommand.Path -Parent 16 | $jsonfilepath = "$basepath\data.json" 17 | Set-Location $basepath 18 | 19 | #Import-Module $basepath -Force -ErrorAction Stop 20 | 21 | #$ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText("$basepath\Get-ParamHelp.ps1"))), $null, $null) 22 | #. $basepath\Get-ParamHelp.ps1 23 | Import-Module $basepath -Force 24 | #region Sample Data 25 | 26 | $SampleParamJson = @" 27 | { 28 | "Name":"ParameterSetName", 29 | "Type":"Argument", 30 | "Parent":"Parameter", 31 | "Description":"Defines which parameter set a parameter belongs to. Multiple sets can be used. Parameters belonging to other sets will be filtered out if a set parameter is selected.", 32 | "Examples":[ 33 | { 34 | "Id":1, 35 | "Example":"[parameter(ParameterSetName=\"MySetName\")]", 36 | "Description":"Sets the parameter set name to \"MySetName\"." 37 | } 38 | ], 39 | "Links":[ 40 | { 41 | "Uri":"https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 42 | "Description":"Parameter Attribute Declaration (MSDN)" 43 | } 44 | ] 45 | } 46 | "@ 47 | $SampleParamJsonWithoutExamples = @" 48 | { 49 | "Meta": { 50 | "SchemaVersion": "2.0", 51 | "RequiresModuleVersion": "1.1.0", 52 | "Revision": 42, 53 | "Source": "https://raw.githubusercontent.com/JohnRoos/PowerShell/master/Modules/Get-ParamHelp/data.json", 54 | "FileHistory": "https://github.com/JohnRoos/PowerShell/commits/master/Modules/Get-ParamHelp/data.json", 55 | "OnlineVersion": "http://blog.roostech.se/p/advancedfunctions.html", 56 | "Local": { 57 | "DownloadDate": "2018-10-18T19:57:56.6903477+02:00", 58 | "Revision": 0, 59 | "Modified": false 60 | } 61 | }, 62 | "ParamHelp": [ 63 | { 64 | "Name":"ParameterSetName", 65 | "Type":"Argument", 66 | "Parent":"Parameter", 67 | "Description":"Defines which parameter set a parameter belongs to. Multiple sets can be used. Parameters belonging to other sets will be filtered out if a set parameter is selected.", 68 | "Links":[ 69 | { 70 | "Uri":"https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 71 | "Description":"Parameter Attribute Declaration (MSDN)" 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | "@ 78 | $SampleParamJsonWithoutName = @" 79 | { 80 | "Meta": { 81 | "SchemaVersion": "2.0", 82 | "RequiresModuleVersion": "1.1.0", 83 | "Revision": 42, 84 | "Source": "https://raw.githubusercontent.com/JohnRoos/PowerShell/master/Modules/Get-ParamHelp/data.json", 85 | "FileHistory": "https://github.com/JohnRoos/PowerShell/commits/master/Modules/Get-ParamHelp/data.json", 86 | "OnlineVersion": "http://blog.roostech.se/p/advancedfunctions.html", 87 | "Local": { 88 | "DownloadDate": "2018-10-18T19:57:56.6903477+02:00", 89 | "Revision": 0, 90 | "Modified": false 91 | } 92 | }, 93 | "ParamHelp": [ 94 | { 95 | "Type":"Argument", 96 | "Parent":"Parameter", 97 | "Description":"Defines which parameter set a parameter belongs to. Multiple sets can be used. Parameters belonging to other sets will be filtered out if a set parameter is selected.", 98 | "Links":[ 99 | { 100 | "Uri":"https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 101 | "Description":"Parameter Attribute Declaration (MSDN)" 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | "@ 108 | $SampleParamJsonWithoutDescription = @" 109 | { 110 | "Meta": { 111 | "SchemaVersion": "2.0", 112 | "RequiresModuleVersion": "1.1.0", 113 | "Revision": 42, 114 | "Source": "https://raw.githubusercontent.com/JohnRoos/PowerShell/master/Modules/Get-ParamHelp/data.json", 115 | "FileHistory": "https://github.com/JohnRoos/PowerShell/commits/master/Modules/Get-ParamHelp/data.json", 116 | "OnlineVersion": "http://blog.roostech.se/p/advancedfunctions.html", 117 | "Local": { 118 | "DownloadDate": "2018-10-18T19:57:56.6903477+02:00", 119 | "Revision": 0, 120 | "Modified": false 121 | } 122 | }, 123 | "ParamHelp": [ 124 | { 125 | "Name":"ParameterSetName", 126 | "Type":"Argument", 127 | "Parent":"Parameter", 128 | "Links":[ 129 | { 130 | "Uri":"https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 131 | "Description":"Parameter Attribute Declaration (MSDN)" 132 | } 133 | ] 134 | } 135 | ] 136 | } 137 | "@ 138 | $SampleParamJsonWithoutNameAndDescription = @" 139 | { 140 | "Meta": { 141 | "SchemaVersion": "2.0", 142 | "RequiresModuleVersion": "1.1.0", 143 | "Revision": 42, 144 | "Source": "https://raw.githubusercontent.com/JohnRoos/PowerShell/master/Modules/Get-ParamHelp/data.json", 145 | "FileHistory": "https://github.com/JohnRoos/PowerShell/commits/master/Modules/Get-ParamHelp/data.json", 146 | "OnlineVersion": "http://blog.roostech.se/p/advancedfunctions.html", 147 | "Local": { 148 | "DownloadDate": "2018-10-18T19:57:56.6903477+02:00", 149 | "Revision": 0, 150 | "Modified": false 151 | } 152 | }, 153 | "ParamHelp": [ 154 | { 155 | "Type":"Argument", 156 | "Parent":"Parameter", 157 | "Links":[ 158 | { 159 | "Uri":"https://msdn.microsoft.com/en-us/library/ms714348(v=vs.85).aspx", 160 | "Description":"Parameter Attribute Declaration (MSDN)" 161 | } 162 | ] 163 | } 164 | ] 165 | } 166 | "@ 167 | 168 | # Sample object which includes everything (normal object) 169 | $SampleParamObject = $SampleParamJson | ConvertFrom-Json 170 | $SampleParamObject.PSObject.TypeNames.Insert(0,'ParameterHelp') 171 | 172 | # Sample object which does not have the correct TypeName 173 | $SampleNonParamObject = $SampleParamJson | ConvertFrom-Json 174 | 175 | # Sample object with correct TypeName but without Examples 176 | $SampleParamObjectWithoutExamples = $SampleParamJsonWithoutExamples | ConvertFrom-Json 177 | $SampleParamObjectWithoutExamples.PSObject.TypeNames.Insert(0,'ParameterHelp') 178 | 179 | #endregion 180 | 181 | #region Unit tests - Helper Functions 182 | 183 | Describe "GetJsonData" { 184 | 185 | Context "Normal json file" { 186 | 187 | It "Runs normally when no parameter is provided" { 188 | GetJsonData | Should Not BeNullOrEmpty 189 | } 190 | 191 | It "Runs normally when a correct Path parameter is provided" { 192 | GetJsonData -Path $jsonfilepath | Should Not BeNullOrEmpty 193 | } 194 | 195 | It "Throws expected error when wrong Path is provided" { 196 | try { 197 | GetJsonData -Path 'TestDrive:\wrong-path\data.json' 198 | Throw "No exception thrown" 199 | } catch { 200 | $_.FullyQualifiedErrorId | Should Be "Path not found: TestDrive:\wrong-path\data.json" 201 | } 202 | } 203 | 204 | It "Throws expected error when file is not a json file" { 205 | try { 206 | GetJsonData -Path $PSCommandPath 207 | Throw "No exception thrown" 208 | } catch { 209 | $_.FullyQualifiedErrorId | Should Be "System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand" 210 | } 211 | } 212 | 213 | It "Throws expected error when Name parameter is " { 214 | try { 215 | GetJsonData -Path $PSCommandPath 216 | Throw "No exception thrown" 217 | } catch { 218 | $_.FullyQualifiedErrorId | Should Be "System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand" 219 | } 220 | } 221 | } 222 | 223 | Context "Abnormal json file" { 224 | 225 | function Get-Content { 'should_not_be_used' } 226 | 227 | 228 | 229 | It "Runs normally when examples are missing in json" { 230 | Mock Get-Content {$SampleParamJsonWithoutExamples} 231 | GetJsonData | Should Not BeNullOrEmpty 232 | } 233 | 234 | It "When examples are missing, ExamplesCount is zero" { 235 | Mock Get-Content { $SampleParamJsonWithoutExamples } 236 | (GetJsonData).ExamplesCount | Should -Be 0 237 | 238 | } 239 | 240 | It "When examples are missing, Name should still be there" { 241 | Mock Get-Content {$SampleParamJsonWithoutExamples} 242 | (GetJsonData).Name | Should Be 'ParameterSetName' 243 | } 244 | 245 | It "Throws expected error if Name is missing" { 246 | Mock Get-Content {$SampleParamJsonWithoutName} 247 | try { 248 | GetJsonData 249 | Throw "No exception thrown" 250 | } catch { 251 | $_.FullyQualifiedErrorId | Should Be "Json file is not valid" 252 | } 253 | } 254 | 255 | It "Throws expected error if Description is missing" { 256 | Mock Get-Content {$SampleParamJsonWithoutDescription} 257 | try { 258 | GetJsonData 259 | Throw "No exception thrown" 260 | } catch { 261 | $_.FullyQualifiedErrorId | Should Be "Json file is not valid" 262 | } 263 | } 264 | 265 | It "Throws expected error if both Name and Description is missing" { 266 | Mock Get-Content {$SampleParamJsonWithoutNameAndDescription} 267 | try { 268 | GetJsonData 269 | Throw "No exception thrown" 270 | } catch { 271 | $_.FullyQualifiedErrorId | Should Be "Json file is not valid" 272 | } 273 | } 274 | 275 | } 276 | } 277 | 278 | Describe "SaveJsonData" { 279 | 280 | Copy-Item -Path $jsonfilepath -Destination 'TestDrive:\data.json' 281 | 282 | It "Runs normally when saving a ParameterHelp object to json" { 283 | SaveJsonData -data $SampleParamObject -Path 'TestDrive:\data.json' | Should BeNullOrEmpty 284 | } 285 | 286 | It "Throws expected error when not using a ParameterHelp object" { 287 | Try { 288 | SaveJsonData -data $SampleNonParamObject -Path 'TestDrive:\data.json' 289 | Throw "No exception thrown" 290 | } catch { 291 | $_.FullyQualifiedErrorId | Should Be 'Data parameter only accepts one or more ParameterHelp objects' 292 | } 293 | } 294 | 295 | it "Throws expected error when Path contains a non-existing directory" { 296 | try { 297 | SaveJsonData -data $SampleParamObject -Path 'TestDrive:\non-existing-folder\data.json' 298 | Throw "No exception thrown" 299 | } catch { 300 | $_.FullyQualifiedErrorId | Should Be 'Directory not found' 301 | } 302 | } 303 | 304 | 305 | } 306 | 307 | Describe "GetPaddedString" { 308 | 309 | It "Returns a string" { 310 | GetPaddedString -Padding 4 -String 'This is a string' | Should BeOfType [string] 311 | } 312 | 313 | It ("Returns a string with one space if no parameters are used") { 314 | GetPaddedString | Should Be " " 315 | } 316 | 317 | It "Pads the string properly when the Padding parameter is used" { 318 | GetPaddedString -Padding 4 -String 'This is a string' | Should Be " This is a string" 319 | } 320 | 321 | It "Pads very long strings into muliple lines" { 322 | # Force the termial width to 50, in case the test is run in a very wide window. 323 | Mock Get-Host { 324 | $props = @{ UI = @{ RawUI = @{ BufferSize = @{ Width = 50} } } } 325 | $result = New-Object -TypeName psobject -Property $props 326 | return $result 327 | } 328 | $longstring = 'Defines which parameter set a parameter belongs to. Multiple sets can be used. Parameters belonging to other sets will be filtered out if a set parameter is selected.' 329 | GetPaddedString -Padding 6 -String $longstring | Should BeLike "*`n *" 330 | } 331 | 332 | } 333 | 334 | Describe "ValidateOneMatch" { 335 | 336 | BeforeAll { 337 | $allobjects = GetJsonData 338 | } 339 | 340 | It "Returns true if only one match is found" { 341 | ValidateOneMatch -Name "Parameter" -allobjects $allobjects | Should Be $true 342 | } 343 | 344 | It "Throws expected error if no match is found" { 345 | try { 346 | ValidateOneMatch -Name "qwerty" -allobjects $allobjects 347 | Throw "No exception thrown" 348 | } catch { 349 | $_.FullyQualifiedErrorId | Should Be "Could not find parameter help content with that name." 350 | } 351 | } 352 | 353 | It "Throws expected error if more than one match is found" { 354 | # set duplicate names (should not exist in the json file) 355 | $allobjects[0].Name = 'Test' 356 | $allobjects[1].Name = 'Test' 357 | try { 358 | ValidateOneMatch -Name "Test" -allobjects $allobjects 359 | Throw "No exception thrown" 360 | } catch { 361 | $_.FullyQualifiedErrorId | Should Be "Found multiple matches. Thats wierd..." 362 | } 363 | } 364 | 365 | It "Throws expected error if the Name parameter is omitted" { 366 | try { 367 | ValidateOneMatch -allobjects $allobjects 368 | Throw "No exception thrown" 369 | } catch { 370 | $_.FullyQualifiedErrorId | Should Be "Could not find parameter help content with that name." 371 | } 372 | } 373 | 374 | It "Throws expected rror if the allobjects parameter is omitted" { 375 | try { 376 | ValidateOneMatch -Name "Parameter" 377 | Throw "No exception thrown" 378 | } catch { 379 | $_.FullyQualifiedErrorId | Should Be "Could not find parameter help content with that name." 380 | } 381 | } 382 | 383 | } 384 | 385 | #endregion 386 | 387 | #region unit tests - Advanced functions 388 | 389 | $baseprops = @{ 390 | 'Name' = [string] 391 | 'Type' = [string] 392 | 'Parent' = [string] 393 | 'Description' = [string] 394 | 'Examples' = [psobject] 395 | 'Links' = [psobject] 396 | 'ExamplesCount' = [system.object] 397 | } 398 | 399 | $arrayprops = 'Examples', 'Links' 400 | 401 | $exampleprops = @{ 402 | Id = [int] 403 | Example = [string] 404 | Description = [string] 405 | } 406 | $linkprops = @{ 407 | Uri = [string] 408 | Description = [string] 409 | } 410 | 411 | Describe "Get-ParamHelp" -tag 'new' { 412 | 413 | Context "Input" { 414 | 415 | It "Executes with no error when parameters are omitted" { 416 | { Get-ParamHelp } | Should Not Throw 417 | } 418 | 419 | It "Returns a string when parameter Name is used" { 420 | $Name = Get-ParamHelp | Select-Object -ExpandProperty Name -First 1 421 | Get-ParamHelp -Name $Name | Should BeOfType [system.string] 422 | } 423 | 424 | It "Throws expected error when wrong name is used" { 425 | try { 426 | Get-ParamHelp -Name "This should not match anything" 427 | Throw "No exception thrown" 428 | } catch { 429 | $_.FullyQualifiedErrorId | Should BeLike 'Could not find help content searching for*' 430 | } 431 | } 432 | 433 | It "Returns ParameterHelp objects when the Raw parameter is used" { 434 | $Name = Get-ParamHelp | Select-Object -ExpandProperty Name -First 1 435 | (Get-ParamHelp -Name $Name -Raw).psobject.TypeNames[0] | Should Be 'ParameterHelp' 436 | } 437 | 438 | It "Returns one object when a full name is used" { 439 | $Name = Get-ParamHelp | Select-Object -ExpandProperty Name -First 1 440 | (Get-ParamHelp -Name $Name).count | Should Be 1 441 | } 442 | 443 | It "Returns the correct amount of objects when no parameters are used" { 444 | $count = (Get-Content -Path data.json | ConvertFrom-Json).ParamHelp.Count 445 | (Get-ParamHelp).count | Should Be $count 446 | } 447 | 448 | It "Opens a Url if the Online parameter is used" { 449 | Mock Start-Process {return $FilePath} 450 | Get-ParamHelp -Name Mandatory -Online | Should Be 'http://blog.roostech.se/p/advancedfunctions.html#Mandatory' 451 | } 452 | 453 | } 454 | 455 | Context "Output objects" { 456 | $Name = Get-ParamHelp | Select-Object -ExpandProperty Name -First 1 457 | $object = Get-ParamHelp -Name $Name -Raw 458 | foreach ($key in $baseprops.Keys) { 459 | It "Output object has a property called $key" { 460 | $object.psobject.Properties.Name -contains $key | Should Be $true 461 | } 462 | 463 | if (![string]::IsNullOrEmpty($object.$key)) { 464 | It "Property $key is of type $($baseprops.$key)" { 465 | $object.$key | Should BeOfType $baseprops.$key 466 | } 467 | } 468 | } 469 | 470 | if ($object.Examples.count -gt 0) { 471 | $exID = 0 472 | foreach ($example in $object.Examples) { 473 | $exID++ 474 | foreach ($exampleprop in $exampleprops.Keys) { 475 | 476 | It "Example $exID contains the property $exampleprop" { 477 | $example.psobject.Properties.Name -contains $exampleprop | Should be $true 478 | } 479 | 480 | It "Example $exID property $exampleprop is of type $($exampleprops[$exampleprop])" { 481 | $example.$exampleprop | Should BeOfType $exampleprops[$exampleprop] 482 | } 483 | 484 | } 485 | } 486 | } 487 | } 488 | } 489 | 490 | <# 491 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Set-ParamHelp' } 492 | #> 493 | 494 | Describe "Set-ParamHelp" { 495 | 496 | # verify that mocking works 497 | # (might not work under certain conditions, like duplicate functions loaded etc.) 498 | Mock SaveJsonData { return 'SaveJsonData is mocked' } 499 | 500 | It 'Function SaveJsonData is mocked' { 501 | SaveJsonData | Should Be 'SaveJsonData is mocked' 502 | } 503 | 504 | if (SaveJsonData -eq 'SaveJsonData is mocked') { 505 | 506 | # Mock SaveJsonData properly before doing proper tests 507 | Mock SaveJsonData { return ([PSCustomObject]@{ obj=$data}) } 508 | 509 | It 'Changes name on parameter help object' { 510 | $test = Set-ParamHelp -Name 'Parameter' -NewName 'ParameterTEST' 511 | $test.obj | Where-Object name -eq 'ParameterTEST' | Select-Object -ExpandProperty Name | Should Be 'ParameterTEST' 512 | } 513 | 514 | It 'Changes description on parameter help object' { 515 | $test = Set-ParamHelp -Name 'Parameter' -Type 'TestType' 516 | $test.obj | Where-Object Name -eq 'Parameter' | Select-Object -ExpandProperty Type | Should Be 'TestType' 517 | } 518 | 519 | It 'Changes description on parameter help object' { 520 | $test = Set-ParamHelp -Name 'Parameter' -Description 'TestDescription' 521 | $test.obj | Where-Object Name -eq 'Parameter' | Select-Object -ExpandProperty Description | Should Be 'TestDescription' 522 | } 523 | 524 | It 'Changes parent on parameter help object' { 525 | $test = Set-ParamHelp -Name 'Parameter' -Parent "TestParent" 526 | $test.obj | Where-Object name -eq 'Parameter' | Select-Object -ExpandProperty Parent | Should Be 'TestParent' 527 | } 528 | 529 | } 530 | } 531 | 532 | <# 533 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Add-ParamHelp' } 534 | #> 535 | 536 | Describe "Add-ParamHelp" { 537 | 538 | # verify that mocking works 539 | # (might not work under certain conditions, like duplicate functions loaded etc.) 540 | Mock SaveJsonData { return 'SaveJsonData is mocked' } 541 | 542 | It 'Function SaveJsonData is mocked' { 543 | SaveJsonData | Should Be 'SaveJsonData is mocked' 544 | } 545 | 546 | if (SaveJsonData -eq 'SaveJsonData is mocked') { 547 | 548 | # Mock SaveJsonData properly before doing proper tests 549 | Mock SaveJsonData { return ([PSCustomObject]@{ obj=$data}) } 550 | 551 | $result = Add-ParamHelp -Name '_TestName_' -Type '_TestType_' -Parent '_TestParent_' -Description '_TestDescription_' 552 | $test = $result.obj | Where-Object -Property Name -eq '_TestName_' 553 | 554 | It "Saves the Name of the new help object" { 555 | $test.name | Should Be '_TestName_' 556 | } 557 | 558 | It "Saves the Type of the new help object" { 559 | $test.Type | Should Be '_TestType_' 560 | } 561 | 562 | It "Saves the Parent of the new help object" { 563 | $test.Parent | Should Be '_TestParent_' 564 | } 565 | 566 | It "Saves the Description of the new help object" { 567 | $test.Description | Should Be '_TestDescription_' 568 | } 569 | 570 | It "Throws expected error if the Name already exist" { 571 | Try { 572 | $name = Get-ParamHelp | Select-Object -First 1 -ExpandProperty Name 573 | Add-ParamHelp -Name $name -Type 'x' -Parent 'x' -Description 'x' -ErrorAction Stop 574 | Throw "No error thrown." 575 | } catch { 576 | $_.Exception.Message | Should Be 'Name already exist' 577 | } 578 | } 579 | 580 | } 581 | } 582 | 583 | 584 | <# 585 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Add-ParamHelpExample' } 586 | #> 587 | 588 | Describe "Add-ParamHelpExample" { 589 | 590 | # verify that mocking works 591 | # (might not work under certain conditions, like duplicate functions loaded etc.) 592 | Mock SaveJsonData { return 'SaveJsonData is mocked' } 593 | 594 | It 'Function SaveJsonData is mocked' { 595 | SaveJsonData | Should Be 'SaveJsonData is mocked' 596 | } 597 | 598 | if (SaveJsonData -eq 'SaveJsonData is mocked') { 599 | 600 | # Mock SaveJsonData properly before doing proper tests 601 | Mock SaveJsonData { return ([PSCustomObject]@{ obj=$data}) } 602 | 603 | $name = Get-ParamHelp | Select-Object -First 1 -ExpandProperty Name 604 | $result = Add-ParamHelpExample -Name $name -Example '_TestExample_' -ExampleDescription '_TestExampleDescription_' 605 | $examples = $result.obj | Where-Object -Property Name -eq $name | Select-Object -ExpandProperty Examples 606 | $test = $examples[$examples.count-1] 607 | 608 | It 'Adds example code' { 609 | $test.Example | Should Be '_TestExample_' 610 | } 611 | 612 | It 'Adds example description' { 613 | $test.Description | Should Be '_TestExampleDescription_' 614 | } 615 | 616 | It 'Throws expected error if wrong name is used' { 617 | Try { 618 | Add-ParamHelpExample -Name 'some wrong name' -Example 'x' -ExampleDescription 'x' 619 | Throw "No error thrown" 620 | } catch { 621 | $_.FullyQualifiedErrorId | Should Be 'Could not find parameter help content with that name.' 622 | } 623 | } 624 | } 625 | } 626 | 627 | <# 628 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Set-ParamHelpExample' } 629 | #> 630 | 631 | Describe "Set-ParamHelpExample" { 632 | 633 | # verify that mocking works 634 | # (might not work under certain conditions, like duplicate functions loaded etc.) 635 | Mock SaveJsonData { return 'SaveJsonData is mocked' } 636 | 637 | It 'Function SaveJsonData is mocked' { 638 | SaveJsonData | Should Be 'SaveJsonData is mocked' 639 | } 640 | 641 | if ((SaveJsonData -Path 'TESTDRIVE:\data.json') -eq 'SaveJsonData is mocked') { 642 | 643 | # Mock SaveJsonData properly before doing proper tests 644 | Mock SaveJsonData { return ([PSCustomObject]@{ obj=$data}) } 645 | 646 | $name = Get-ParamHelp | Where-Object -Property ExamplesCount -gt 0 | Select-Object -First 1 -ExpandProperty Name 647 | $result = Set-ParamHelpExample -Name $Name -ExampleId 1 -ExampleCode '_ExampleCode_' -ExampleDescription '_ExampleDescription_' 648 | $test = $result.obj | Where-Object -Property Name -eq $name | Select-Object -ExpandProperty Examples | Where-Object -Property Id -eq 1 649 | 650 | 651 | It 'Changes code in example' { 652 | $test.Example | Should Be '_ExampleCode_' 653 | } 654 | 655 | It 'Changes description in example' { 656 | $test.Description | Should Be '_ExampleDescription_' 657 | } 658 | 659 | } 660 | } 661 | 662 | <# 663 | Invoke-Pester .\Get-ParamHelp.tests.ps1 -CodeCoverage @{Path = '.\Get-ParamHelp.ps1'; Function = 'Remove-ParamHelpExample' } 664 | #> 665 | 666 | Describe "Remove-ParamHelpExample" { 667 | 668 | # verify that mocking works 669 | # (might not work under certain conditions, like duplicate functions loaded etc.) 670 | function SaveJsonData {} 671 | 672 | Mock SaveJsonData { 'SaveJsonData is mocked' } 673 | 674 | It 'Function SaveJsonData is mocked' { 675 | SaveJsonData | Should Be 'SaveJsonData is mocked' 676 | } 677 | 678 | if ((SaveJsonData -Path 'TESTDRIVE:\data.json') -eq 'SaveJsonData is mocked') { 679 | 680 | # Mock SaveJsonData before doing tests 681 | Mock SaveJsonData {param($data) $data } 682 | 683 | $sample = Get-ParamHelp | Where-Object -Property ExamplesCount -gt 0 | Select-Object -First 1 684 | $result = Remove-ParamHelpExample -Name $sample.Name -ExampleId 1 -Confirm:$false 685 | $test = $result | Where-Object -Property Name -eq $sample.Name 686 | 687 | 688 | <# 689 | Need to add support for ShouldProcess 690 | #> 691 | 692 | It 'Removes example' { 693 | $sample.Examples.Count - $test.Examples.Count | Should Be 1 694 | } 695 | 696 | It 'Throws expected exception if example id cannot be found' { 697 | try { 698 | Remove-ParamHelpExample -Name $sample.Name -ExampleId 998, 999 -Confirm:$false 699 | Throw 'No exception thrown' 700 | } catch { 701 | $_.FullyQualifiedErrorId | Should Be 'Could not find example 998, 999.' 702 | } 703 | } 704 | 705 | } 706 | 707 | } 708 | #endregion 709 | -------------------------------------------------------------------------------- /Modules/IniManager/IniManager.psm1: -------------------------------------------------------------------------------- 1 | #REQUIRES -Version 4.0 2 | 3 | 4 | <# 5 | .Synopsis 6 | Reads an ini file and creates an object based on the content of the file 7 | .DESCRIPTION 8 | Reads an ini file and creates an object based on the content of the file. One property per key/value. Sections will be named with surrounding brackets and will contain a list of objects based on the keys within that section. 9 | Comments will be ignored. 10 | 11 | Created by John Roos 12 | Email: john@roostech.se 13 | Web: http://blog.roostech.se 14 | .EXAMPLE 15 | get-ini -Path "C:\config.ini" 16 | 17 | Opens the file config.ini and creates an object based on that file. 18 | .OUTPUTS 19 | Outputs an custom object of the type File.Ini 20 | #> 21 | function Get-Ini { 22 | [CmdletBinding()] 23 | param( 24 | # Enter the path for the ini file 25 | [Parameter(Mandatory=$true, 26 | ValueFromPipelineByPropertyName=$true, 27 | Position=0)] 28 | [string]$Path 29 | ) 30 | 31 | Process{ 32 | if (!(Test-Path $Path)) { 33 | Write-Error 'Invalid path' 34 | break 35 | } 36 | 37 | $iniFile = Get-Content $Path -Verbose:$false 38 | $currentSection = '' 39 | $currentKey = '' 40 | $currentValue = '' 41 | 42 | [hashtable]$iniSectionHash = [ordered]@{} 43 | [hashtable]$iniConfigArray = [ordered]@{} 44 | 45 | foreach ($line in $iniFile) { 46 | if ( $line.Trim().StartsWith('[') -and $line.EndsWith(']') ) { 47 | Write-Verbose "Found new section." 48 | if ($currentSection -ne ''){ 49 | Write-Verbose "Creating section property based on array:" 50 | $keyobj = New-Object PSObject -Property $iniConfigArray 51 | $keyobj.PSObject.TypeNames.Insert(0,'File.Ini.Config') 52 | $iniSectionHash.Add($currentSection,$keyobj) 53 | [hashtable]$iniConfigArray = @{} 54 | Write-Verbose "Created section property: $currentSection" 55 | } 56 | if ($iniConfigArray.count -gt 0) { 57 | $rootSection = $iniConfigArray 58 | [hashtable]$iniConfigArray = [ordered]@{} 59 | } 60 | $currentSection = $line 61 | Write-Verbose "Current section: $currentSection" 62 | continue 63 | } 64 | Write-Verbose "Parsing line: $line" 65 | if ( $line.Contains('=') ){ 66 | $keyvalue = $line.Split('=') 67 | [string]$currentKey = $keyvalue[0] 68 | [string]$currentValue = $keyvalue[1] 69 | $valuehash = @{ 70 | $currentKey = $currentValue 71 | } 72 | $iniConfigArray.Add($currentKey, $currentValue) 73 | Write-Verbose "Added keyvalue: $($keyvalue[0]) = $($keyvalue[1])" 74 | } 75 | <# below was for handling comments, but I wont do it... 76 | elseif ($line.Contains('#') -or $line.Contains(';')) { 77 | [string]$currentKey = $line 78 | [string]$currentValue = "" 79 | $valuehash = @{ 80 | $currentKey = $currentValue 81 | } 82 | $iniConfigArray.Add($currentKey, $currentValue) 83 | Write-Verbose "Added comment: $currentKey" 84 | }#> 85 | } 86 | $keyobj = New-Object PSObject -Property $iniConfigArray 87 | $keyobj.PSObject.TypeNames.Insert(0,'File.ini.Section') 88 | $iniSectionHash.Add($currentSection,$keyobj) 89 | Write-Verbose "Created last section property: $currentSection" 90 | $result = New-Object PSObject -Property $iniSectionHash 91 | if ($rootSection) { 92 | foreach ($key in $rootSection.keys){ 93 | Add-Member -InputObject $result -MemberType NoteProperty -Name $key -Value $rootSection.$key 94 | } 95 | } 96 | $result.PSObject.TypeNames.Insert(0,'File.ini') 97 | Return $result 98 | } 99 | } 100 | 101 | <# 102 | .Synopsis 103 | Sets a specific key to a value in a ini file 104 | .DESCRIPTION 105 | Sets a specific key to a value in a ini file 106 | Comments will be ignored. 107 | Warning: Even comments in the target ini file will be removed! 108 | 109 | Created by John Roos 110 | Email: john@roostech.se 111 | Web: http://blog.roostech.se 112 | .EXAMPLE 113 | Set-IniKey -Path "C:\config.ini" -Key LoggingLevel -Value Debug -Section Logging -Encoding UTF8 114 | 115 | Opens the file config.ini and changes the key "LoggingLevel" in the [Logging] section of the file. The file will be saved with UTF8 encoding. 116 | .OUTPUTS 117 | Creates a ini file. Keeps the original content of the ini file and only replaces the value for the key matching the parameter Key 118 | #> 119 | function Set-IniKey { 120 | [CmdletBinding()] 121 | param( 122 | [Parameter(Mandatory=$true, 123 | Position=0)] 124 | [string]$Path, 125 | 126 | [Parameter(Mandatory=$true, 127 | Position=1)] 128 | [String]$Key, 129 | 130 | [Parameter(Mandatory=$true, 131 | Position=2)] 132 | [String]$Value, 133 | 134 | [Parameter(Mandatory=$false, 135 | Position=3)] 136 | [String]$Section, 137 | 138 | [Parameter(Mandatory=$false, 139 | ValueFromPipeline=$false, 140 | Position=4)] 141 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 142 | [psobject]$Encoding = "UTF8", 143 | 144 | [Parameter(Mandatory=$False, 145 | ValueFromPipeline=$False, 146 | Position=5)] 147 | [switch]$Force 148 | ) 149 | 150 | Process { 151 | if ($Section){ 152 | $Section = $Section.Replace('[','').Replace(']','') 153 | $Section = "[$Section]" 154 | } 155 | 156 | Write-Verbose "Checking if path exists" 157 | if (Test-Path $Path){ 158 | Write-Verbose "Path exists" 159 | Write-Verbose "Reading ini file with Get-Ini" 160 | $ini = Get-Ini -Path $Path 161 | Write-Verbose "Get-Ini completed" 162 | } else { 163 | Write-Error 'Path does not exist' 164 | break 165 | } 166 | 167 | if ($Section){ 168 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 169 | Write-Verbose "Checking if the section $Section already exist" 170 | if (!$availableSections.Contains($Section.tolower())) { 171 | Write-Verbose "Section does not exist" 172 | Write-Host 'Creating new section' 173 | $props = [hashtable]@{$Key = $Value} 174 | $newValue = New-Object PSObject -Property $props 175 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Section -Value $newValue 176 | } else { 177 | Write-Verbose "Section exist" 178 | [array]$availableProperties = ($ini.$Section | Get-Member -MemberType Properties | Where-Object { $_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty' } | Select-Object -ExpandProperty Name).ToLower() 179 | if (!$availableProperties.Contains($Key.ToLower())) { 180 | Write-Verbose "Property $Property exist" 181 | Write-Verbose "Setting property value to $Value" 182 | $ini.$Section | Add-Member -MemberType NoteProperty -Name $Key -Value $Value 183 | } else { 184 | Write-Verbose 'Adding property' 185 | $ini.$Section.$Key = $Value 186 | } 187 | } 188 | } else { 189 | [array]$availableProps = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 190 | if (!$availableProps.Contains($Key.ToLower())) { 191 | Write-Verbose "Property $Key does not exist" 192 | Write-Verbose "Creating new property $Key" 193 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Key -Value $Value 194 | } else { 195 | Write-Verbose "Property $Key exist" 196 | Write-Verbose "Setting property value to $Value" 197 | $ini.$Key = $Value 198 | } 199 | } 200 | 201 | Write-Verbose 'Creating ini file with New-Ini' 202 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force 203 | } 204 | } 205 | 206 | <# 207 | .Synopsis 208 | Removes a key (entire line) in ini file 209 | .DESCRIPTION 210 | Removes a key (entire line) in ini file 211 | Comments will be ignored. 212 | Warning: Even comments in the target ini file will be removed! 213 | 214 | Created by John Roos 215 | Email: john@roostech.se 216 | Web: http://blog.roostech.se 217 | .EXAMPLE 218 | Remove-IniKey -Path "c:\config.ini" -Key [system]Proxy -Encoding ASCII 219 | 220 | Opens the file config.ini and removes the key "Proxy" in the [system] section of the file. The file will be saved with ASCII encoding. 221 | .OUTPUTS 222 | Overwrites the original ini file 223 | #> 224 | function Remove-IniKey { 225 | [CmdletBinding()] 226 | param( 227 | [Parameter(Mandatory=$true, 228 | Position=0)] 229 | [string]$Path, 230 | 231 | [Parameter(Mandatory=$true, 232 | Position=1)] 233 | [string]$Key, 234 | 235 | [Parameter(Mandatory=$false, 236 | Position=2)] 237 | [string]$Section, 238 | 239 | [Parameter(Mandatory=$false, 240 | Position=3)] 241 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 242 | [string]$Encoding = "UTF8", 243 | 244 | [Parameter(Mandatory=$False, 245 | Position=4)] 246 | [switch]$Force 247 | ) 248 | 249 | Process { 250 | 251 | Write-Verbose "Checking path to ini file" 252 | if (Test-Path $Path) { 253 | Write-Verbose "Ini file found" 254 | Write-Verbose "Reading ini file with Get-Ini" 255 | $ini = Get-Ini -Path $Path -Verbose:$false 256 | Write-Verbose "Get-Ini completed" 257 | } else { 258 | Write-Error 'Cannot find ini file' 259 | break 260 | } 261 | 262 | if ($Section) { 263 | $Section = $Section.Replace('[','').Replace(']','') 264 | $inisection = "[$Section]" 265 | Write-Verbose "Key belongs to section $inisection" 266 | } else { 267 | $inisection = $null 268 | Write-Verbose 'Key does not belong to any section' 269 | } 270 | 271 | $iniproperty = $Key 272 | 273 | # check if the value contains a [section] 274 | <# 275 | if ($Value -like '*]*') { 276 | $tempvalue = $Key.Trim().Split(']') 277 | $inisection = "$($tempvalue[0].Trim())]" 278 | $iniproperty = "$($tempvalue[1].Trim())" 279 | Write-Verbose "Section: $inisection" 280 | Write-Verbose "Property: $iniproperty" 281 | } else { 282 | $inisection = "" 283 | $iniproperty = $Key 284 | Write-Verbose "No section selected" 285 | Write-Verbose "Property: $iniproperty" 286 | } 287 | #> 288 | Write-Verbose 'Key check completed' 289 | Write-Verbose 'Searching for matching keys in ini file' 290 | 291 | if ($inisection) { 292 | # if section was included in the Value parameter 293 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 294 | if ($availableSections.Contains($inisection.ToLower())) { 295 | # Section exists in the ini file 296 | Write-Verbose "Section $inisection exists" 297 | [array]$availableProps = ($ini.$inisection | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 298 | if ($availableProps.Contains($iniproperty.ToLower())) { 299 | # Property exists in the correct section in the ini file 300 | Write-Verbose "Property $iniproperty exists" 301 | $ini.$inisection.PSObject.Properties.Remove($iniproperty) 302 | Write-Verbose 'Key removed from ini object' 303 | } else { 304 | Write-Error "ini file contains the section but does not contain requested key: $iniproperty" 305 | break 306 | } 307 | } else { 308 | Write-Error "ini file does not contain requested section: $inisection" 309 | break 310 | } 311 | 312 | } else { 313 | # if no section was included in the Value parameter 314 | if ($ini.psobject.properties.name.Contains($Key)) { 315 | $ini.PSObject.Properties.Remove($Key) 316 | Write-Verbose 'Key removed from ini object' 317 | } else { 318 | Write-Error 'Key does not exist in ini file' 319 | break 320 | } 321 | 322 | } 323 | 324 | #$ini 325 | # Recreate the ini file 326 | Write-Verbose 'Saving file' 327 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 328 | } 329 | } 330 | 331 | 332 | <# 333 | .Synopsis 334 | Renames a key in ini file 335 | .DESCRIPTION 336 | Renames a key in ini file 337 | Comments will be ignored. 338 | Warning: Even comments in the target ini file will be removed! 339 | 340 | Created by John Roos 341 | Email: john@roostech.se 342 | Web: http://blog.roostech.se 343 | .EXAMPLE 344 | Rename-IniKey -Path c:\config.ini -Key Prixy -NewKey Proxy -Section system -Encoding UTF8 345 | 346 | Opens the file config.ini and renames the key "Prixy" to "Proxy" in the [system] section of the file. The file will be saved with UTF8 encoding. 347 | .OUTPUTS 348 | Overwrites the original ini file 349 | #> 350 | function Rename-IniKey { 351 | [CmdletBinding()] 352 | param( 353 | [Parameter(Mandatory=$true, 354 | Position=0)] 355 | [string]$Path, 356 | 357 | [Parameter(Mandatory=$true, 358 | Position=1)] 359 | [string]$Key, 360 | 361 | [Parameter(Mandatory=$true, 362 | Position=2)] 363 | [string]$NewKey, 364 | 365 | [Parameter(Mandatory=$false, 366 | Position=3)] 367 | [string]$Section, 368 | 369 | [Parameter(Mandatory=$false, 370 | Position=4)] 371 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 372 | [string]$Encoding = "UTF8", 373 | 374 | [Parameter(Mandatory=$False, 375 | Position=5)] 376 | [switch]$Force 377 | ) 378 | 379 | Process { 380 | 381 | Write-Verbose "Checking if path exists" 382 | if (Test-Path $Path) { 383 | Write-Verbose "Path exists" 384 | Write-Verbose "Reading ini file with Get-Ini" 385 | $ini = Get-Ini -Path $Path -Verbose:$false 386 | Write-Verbose "Get-Ini completed" 387 | } else { 388 | Write-Error 'Path does not exist' 389 | break 390 | } 391 | 392 | Write-Verbose 'Checking value to remove' 393 | 394 | if ($Section) { 395 | $Section = $Section.Replace('[','').Replace(']','') 396 | $inisection = "[$Section]" 397 | } else { 398 | $inisection = $null 399 | } 400 | 401 | $iniproperty = $Key 402 | 403 | # check if the value contains a [section] 404 | <# 405 | if ($Value -like '*]*') { 406 | $tempvalue = $Key.Trim().Split(']') 407 | $inisection = "$($tempvalue[0].Trim())]" 408 | $iniproperty = "$($tempvalue[1].Trim())" 409 | Write-Verbose "Section: $inisection" 410 | Write-Verbose "Property: $iniproperty" 411 | } else { 412 | $inisection = "" 413 | $iniproperty = $Key 414 | Write-Verbose "No section selected" 415 | Write-Verbose "Property: $iniproperty" 416 | } 417 | #> 418 | Write-Verbose 'Value check completed' 419 | Write-Verbose 'Checking object for matching properties' 420 | 421 | if ($inisection) { 422 | # if section was included in the Value parameter 423 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 424 | if ($availableSections.Contains($inisection.ToLower())) { 425 | # Section exists in the ini file 426 | Write-Verbose "Section $inisection exists" 427 | [array]$availableProps = ($ini.$inisection | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 428 | if ($availableProps.Contains($iniproperty.ToLower())) { 429 | # Property exists in the correct section in the ini file 430 | Write-Verbose "Property $iniproperty exists" 431 | $tempvalue = $ini.$inisection.$iniproperty 432 | Write-Verbose "Removing property" 433 | $ini.$inisection.PSObject.Properties.Remove($iniproperty) 434 | Write-Verbose "Recreating property with new name and same value" 435 | $ini.$inisection | Add-Member -MemberType NoteProperty -Name $NewKey -Value $tempvalue 436 | } else { 437 | Write-Error "ini file contains the section but does not contain requested configuration: $iniproperty" 438 | break 439 | } 440 | } else { 441 | Write-Error "ini file does not contain requested section: $inisection" 442 | break 443 | } 444 | 445 | } else { 446 | # if no section was included in the Value parameter 447 | if ($ini.psobject.properties.name.Contains($Key)) { 448 | $tempvalue = $ini.$iniproperty 449 | Write-Verbose "Removing property" 450 | $ini.PSObject.Properties.Remove($iniproperty) 451 | Write-Verbose "Recreating property with new name and same value" 452 | $ini | Add-Member -MemberType NoteProperty -Name $NewKey -Value $tempvalue 453 | Write-Verbose 'Value removed from ini object' 454 | 455 | } else { 456 | Write-Error 'Key does not exist in ini file' 457 | break 458 | } 459 | 460 | } 461 | 462 | #$ini 463 | # Recreate the ini file 464 | Write-Verbose 'Saving file' 465 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 466 | } 467 | } 468 | 469 | 470 | <# 471 | .Synopsis 472 | Sets a number of keys and values in ini file based on a hash table 473 | .DESCRIPTION 474 | Sets a number of keys and values in ini file based on a hash table. Sections are separated by naming them within brackets, like this: [section]key 475 | The keys will be added if they do not exist. 476 | Comments will be ignored. 477 | Warning: Even comments in the target ini file will be removed! 478 | 479 | Created by John Roos 480 | Email: john@roostech.se 481 | Web: http://blog.roostech.se 482 | .EXAMPLE 483 | Set-IniFromHash -Path c:\config.ini -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} -Encoding UTF8 484 | 485 | Opens the file config.ini and sets the key DebugLog to false and in the [settings] section sets the Hostname to localhost. 486 | .OUTPUTS 487 | Overwrites the original ini file 488 | #> 489 | function Set-IniFromHash { 490 | [CmdletBinding()] 491 | param( 492 | [Parameter(Mandatory=$true, 493 | Position=0)] 494 | [string]$Path, 495 | 496 | [Parameter(Mandatory=$true, 497 | ValueFromPipeline=$true, 498 | Position=1)] 499 | [hashtable]$Values, 500 | 501 | [Parameter(Mandatory=$false, 502 | ValueFromPipeline=$false, 503 | Position=2)] 504 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 505 | [psobject]$Encoding = "UTF8", 506 | 507 | [Parameter(Mandatory=$False, 508 | ValueFromPipeline=$False, 509 | Position=3)] 510 | [switch]$Force 511 | ) 512 | 513 | Process { 514 | [string]$section = '' 515 | 516 | Write-Verbose "Checking if path exists" 517 | if (Test-Path $Path) { 518 | Write-Verbose "File exists" 519 | $iniexist = $true 520 | Write-Verbose "Reading ini file with Get-Ini" 521 | $ini = Get-Ini -Path $Path -Verbose:$false 522 | Write-Verbose "Get-Ini completed" 523 | } else { 524 | if ($Force){ 525 | $ini = New-Object -TypeName psobject 526 | Write-Verbose 'File does not exist (new file will be created)' 527 | $iniexist = $false 528 | } else { 529 | Write-Error 'File does not exist. Use the Force parameter to create file.' 530 | break 531 | } 532 | } 533 | 534 | [array]$availableSections = @() 535 | 536 | if ($iniexist) { 537 | $availableSections = ( $ini | 538 | Get-Member -MemberType Properties | 539 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 540 | Select-Object -ExpandProperty Name | 541 | Where-Object {$_.Contains(']')} ).tolower() 542 | } 543 | 544 | foreach ($key in $Values.keys) { 545 | Write-Verbose "Processing $key" 546 | if ($key -like '*]*') { 547 | Write-Verbose "Section selected" 548 | $keysplit = $key.Split(']') 549 | $section = $keysplit[0] + ']' 550 | $property = $keysplit[1] 551 | 552 | 553 | if ($availableSections.Contains($section.ToLower())) { 554 | Write-Verbose "Section $section exists" 555 | 556 | [array]$availableProps = ( $ini.$section | 557 | Get-Member -MemberType Properties | 558 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 559 | Select-Object -ExpandProperty Name | 560 | Where-Object {!$_.Contains(']')} ).ToLower() 561 | 562 | if (!$availableProps.Contains($property.ToLower())) { 563 | Write-Verbose "Property $property does not exist" 564 | Write-Verbose "Creating new property $property" 565 | Add-Member -InputObject $ini.$section -MemberType NoteProperty -Name $property -Value $Value 566 | } else { 567 | Write-Verbose "Property $property exist" 568 | Write-Verbose "Setting property value to $Value" 569 | $ini.$section.$Property = $Value 570 | } 571 | } else { 572 | Write-Verbose "Section $section does not exist !one!" 573 | $props = [hashtable]@{$property = $Values.$key} 574 | $newValue = New-Object PSObject -Property $props 575 | $availableSections += $section.ToLower() 576 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Section -Value $newValue 577 | } 578 | $ini.$section.$property = $Values.$key 579 | } else { 580 | Write-Verbose "No section selected" 581 | $property = $key 582 | [array]$availableProps = @() 583 | 584 | if ($iniexist) { 585 | [array]$availableProps = ($ini | 586 | Get-Member -MemberType Properties | 587 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 588 | Select-Object -ExpandProperty Name | 589 | Where-Object {!$_.Contains(']')}).ToLower() 590 | } 591 | 592 | if (!$availableProps.Contains($property.ToLower())) { 593 | Write-Verbose "Property $property does not exist" 594 | Write-Verbose "Creating new property $property" 595 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $property -Value $Values.$key 596 | } else { 597 | Write-Verbose "Property $property exist" 598 | Write-Verbose "Setting property value to $($Values.$key)" 599 | $ini.$Property = $Values.$key 600 | } 601 | } 602 | } 603 | # uncomment below for debug 604 | # $ini 605 | Write-Verbose "Saving file $Path with encoding $Encoding" 606 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 607 | } 608 | } 609 | 610 | 611 | 612 | 613 | 614 | <# 615 | .Synopsis 616 | Creates an ini file based on a custom object of the type File.Ini 617 | .DESCRIPTION 618 | Creates an ini file based on a custom object of the type File.Ini 619 | Comments will be ignored. 620 | 621 | Created by John Roos 622 | Email: john@roostech.se 623 | Web: http://blog.roostech.se 624 | .EXAMPLE 625 | get-ini -Path "C:\config.ini" | new-ini -Path c:\config_new.ini -Encoding UTF8 626 | 627 | Opens the file config.ini and which creates a File.Ini object. The object is then piped to New-Ini which will create a new file based on that object. 628 | .OUTPUTS 629 | Creates a new ini file 630 | #> 631 | function New-Ini { 632 | [CmdletBinding()] 633 | param( 634 | [Parameter(Mandatory=$true, 635 | Position=0)] 636 | [string]$Path, 637 | 638 | [Parameter(Mandatory=$true, 639 | ValueFromPipeline=$true, 640 | Position=1)] 641 | [psobject]$Content, 642 | 643 | [Parameter(Mandatory=$false, 644 | ValueFromPipeline=$false, 645 | Position=2)] 646 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 647 | [string]$Encoding = "UTF8", 648 | 649 | [Parameter(Mandatory=$False, 650 | ValueFromPipeline=$False, 651 | Position=3)] 652 | [switch]$Force 653 | ) 654 | 655 | Process { 656 | [array]$result = @() 657 | $sections = $Content | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name 658 | foreach ($section in $sections){ 659 | if ($section -notlike '*]*') { 660 | $result += "$section=$($Content.$section)" 661 | } 662 | } 663 | if ($result.count -gt 0) { 664 | $result += " " 665 | } 666 | foreach ($section in $sections){ 667 | if ($section -like '*]*') { 668 | $result += "$section" 669 | $keys = $Content.$section | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name 670 | foreach ($key in $keys){ 671 | $result += "$key=$($Content.$section.$key)" 672 | } 673 | $result += " " 674 | } 675 | } 676 | $result | Out-File -FilePath $Path -Encoding $Encoding -Force:$Force 677 | } 678 | } 679 | 680 | 681 | <# 682 | .Synopsis 683 | Returns true or false if the ini file has the provided configuration 684 | .DESCRIPTION 685 | Reads the configuration from an ini file and compares the content with the provided hashtable. 686 | Comments will be ignored. 687 | 688 | Created by John Roos 689 | Email: john@roostech.se 690 | Web: http://blog.roostech.se 691 | .EXAMPLE 692 | Test-Ini -Path "C:\config.ini" -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} 693 | 694 | Opens the file config.ini and checks the values for 'DebugLog' (not in any section) and 'Hostname' (in section 'settubgs'). If the values are the same as the provided values the function will return True, otherwise it will return False. 695 | .OUTPUTS 696 | Boolean 697 | #> 698 | function Test-Ini { 699 | [CmdletBinding()] 700 | param( 701 | [Parameter(Mandatory=$true, 702 | Position=0)] 703 | [string]$Path, 704 | 705 | [Parameter(Mandatory=$true, 706 | ValueFromPipeline=$true, 707 | Position=1)] 708 | [hashtable]$Values 709 | ) 710 | 711 | Process { 712 | Write-Verbose "Checking if path exists" 713 | if (Test-Path $Path) { 714 | Write-Verbose "Path exists" 715 | Write-Verbose "Reading ini file with Get-Ini" 716 | $ini = Get-Ini -Path $Path -Verbose:$false 717 | Write-Verbose "Get-Ini completed" 718 | } else { 719 | Write-Error 'Path does not exist' 720 | return $false 721 | } 722 | 723 | foreach ($key in $Values.keys) { 724 | Write-Verbose "Processing $key" 725 | if ($key -like '*]*') { 726 | Write-Verbose "Section selected" 727 | $keysplit = $key.Split(']') 728 | $section = $keysplit[0] + ']' 729 | $property = $keysplit[1] 730 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 731 | if ($availableSections.Contains($section.ToLower())) { 732 | Write-Verbose "Section $section exists" 733 | 734 | [array]$availableProps = ($ini.$section | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 735 | if (!$availableProps.Contains($property.ToLower())) { 736 | Write-Verbose "Property $property does not exist" 737 | Write-Verbose "Property $property NOT OK (ref 1)" 738 | return $false 739 | } else { 740 | Write-Verbose "Property $property exist" 741 | if ($ini.$section.$Property -ne $Values.$key){ 742 | Write-Verbose "Property $property NOT OK (ref 2)" 743 | return $false 744 | } else { 745 | Write-Verbose "Property $property OK (ref 1)" 746 | } 747 | 748 | } 749 | } else { 750 | Write-Verbose "Section $section does not exist" 751 | return $false 752 | } 753 | $ini.$section.$property = $Values.$key 754 | } else { 755 | Write-Verbose "No section selected" 756 | $property = $key 757 | [array]$availableProps = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 758 | if (!$availableProps.Contains($property.ToLower())) { 759 | Write-Verbose "Property $property does not exist" 760 | Write-Verbose "Property $property NOT OK (ref 3)" 761 | return $false 762 | } else { 763 | Write-Verbose "Property $property exist" 764 | if ($ini.$Property -ne $Values.$key) { 765 | Write-Verbose "Property $property NOT OK (ref 4)" 766 | return $false 767 | } else { 768 | Write-Verbose "Property $property OK (ref 2)" 769 | } 770 | 771 | } 772 | } 773 | } 774 | 775 | return $true 776 | } 777 | } -------------------------------------------------------------------------------- /DSC/cIniFile/cIniFile.psm1: -------------------------------------------------------------------------------- 1 | [DscResource()] 2 | class cIniFile 3 | { 4 | 5 | [DscProperty(Key)] 6 | [string] $Path 7 | 8 | [DscProperty(Mandatory)] 9 | [hashtable] $Config 10 | 11 | [void] Set() 12 | { 13 | Set-IniFromHash -Path $this.Path -Values $this.Config -Force 14 | } 15 | 16 | [bool] Test() 17 | { 18 | if (Test-Ini -Path $this.Path -Values $this.Config -ErrorAction SilentlyContinue){ 19 | return $true 20 | } else { 21 | return $false 22 | } 23 | } 24 | 25 | [cIniFile] Get() 26 | { 27 | return $this 28 | } 29 | } 30 | 31 | 32 | 33 | 34 | ################################### 35 | 36 | #REQUIRES -Version 4.0 37 | 38 | 39 | <# 40 | .Synopsis 41 | Reads an ini file and creates an object based on the content of the file 42 | .DESCRIPTION 43 | Reads an ini file and creates an object based on the content of the file. One property per key/value. Sections will be named with surrounding brackets and will contain a list of objects based on the keys within that section. 44 | Comments will be ignored. 45 | 46 | Created by John Roos 47 | Email: john@roostech.se 48 | Web: http://blog.roostech.se 49 | .EXAMPLE 50 | get-ini -Path "C:\config.ini" 51 | 52 | Opens the file config.ini and creates an object based on that file. 53 | .OUTPUTS 54 | Outputs an custom object of the type File.Ini 55 | #> 56 | function Get-Ini { 57 | [CmdletBinding()] 58 | param( 59 | # Enter the path for the ini file 60 | [Parameter(Mandatory=$true, 61 | ValueFromPipelineByPropertyName=$true, 62 | Position=0)] 63 | [string]$Path 64 | ) 65 | 66 | Process{ 67 | if (!(Test-Path $Path)) { 68 | Write-Error 'Invalid path' 69 | break 70 | } 71 | 72 | $iniFile = Get-Content $Path -Verbose:$false 73 | $currentSection = '' 74 | $currentKey = '' 75 | $currentValue = '' 76 | 77 | [hashtable]$iniSectionHash = [ordered]@{} 78 | [hashtable]$iniConfigArray = [ordered]@{} 79 | 80 | foreach ($line in $iniFile) { 81 | if ( $line.Trim().StartsWith('[') -and $line.EndsWith(']') ) { 82 | Write-Verbose "Found new section." 83 | if ($currentSection -ne ''){ 84 | Write-Verbose "Creating section property based on array:" 85 | $keyobj = New-Object PSObject -Property $iniConfigArray 86 | $keyobj.PSObject.TypeNames.Insert(0,'File.Ini.Config') 87 | $iniSectionHash.Add($currentSection,$keyobj) 88 | [hashtable]$iniConfigArray = @{} 89 | Write-Verbose "Created section property: $currentSection" 90 | } 91 | if ($iniConfigArray.count -gt 0) { 92 | $rootSection = $iniConfigArray 93 | [hashtable]$iniConfigArray = [ordered]@{} 94 | } 95 | $currentSection = $line 96 | Write-Verbose "Current section: $currentSection" 97 | continue 98 | } 99 | Write-Verbose "Parsing line: $line" 100 | if ( $line.Contains('=') ){ 101 | $keyvalue = $line.Split('=') 102 | [string]$currentKey = $keyvalue[0] 103 | [string]$currentValue = $keyvalue[1] 104 | $valuehash = @{ 105 | $currentKey = $currentValue 106 | } 107 | $iniConfigArray.Add($currentKey, $currentValue) 108 | Write-Verbose "Added keyvalue: $($keyvalue[0]) = $($keyvalue[1])" 109 | } 110 | <# below was for handling comments, but I wont do it... 111 | elseif ($line.Contains('#') -or $line.Contains(';')) { 112 | [string]$currentKey = $line 113 | [string]$currentValue = "" 114 | $valuehash = @{ 115 | $currentKey = $currentValue 116 | } 117 | $iniConfigArray.Add($currentKey, $currentValue) 118 | Write-Verbose "Added comment: $currentKey" 119 | }#> 120 | } 121 | $keyobj = New-Object PSObject -Property $iniConfigArray 122 | $keyobj.PSObject.TypeNames.Insert(0,'File.ini.Section') 123 | $iniSectionHash.Add($currentSection,$keyobj) 124 | Write-Verbose "Created last section property: $currentSection" 125 | $result = New-Object PSObject -Property $iniSectionHash 126 | if ($rootSection) { 127 | foreach ($key in $rootSection.keys){ 128 | Add-Member -InputObject $result -MemberType NoteProperty -Name $key -Value $rootSection.$key 129 | } 130 | } 131 | $result.PSObject.TypeNames.Insert(0,'File.ini') 132 | Return $result 133 | } 134 | } 135 | 136 | <# 137 | .Synopsis 138 | Sets a specific key to a value in a ini file 139 | .DESCRIPTION 140 | Sets a specific key to a value in a ini file 141 | Comments will be ignored. 142 | Warning: Even comments in the target ini file will be removed! 143 | 144 | Created by John Roos 145 | Email: john@roostech.se 146 | Web: http://blog.roostech.se 147 | .EXAMPLE 148 | Set-IniKey -Path "C:\config.ini" -Key LoggingLevel -Value Debug -Section Logging -Encoding UTF8 149 | 150 | Opens the file config.ini and changes the key "LoggingLevel" in the [Logging] section of the file. The file will be saved with UTF8 encoding. 151 | .OUTPUTS 152 | Creates a ini file. Keeps the original content of the ini file and only replaces the value for the key matching the parameter Key 153 | #> 154 | function Set-IniKey { 155 | [CmdletBinding()] 156 | param( 157 | [Parameter(Mandatory=$true, 158 | Position=0)] 159 | [string]$Path, 160 | 161 | [Parameter(Mandatory=$true, 162 | Position=1)] 163 | [String]$Key, 164 | 165 | [Parameter(Mandatory=$true, 166 | Position=2)] 167 | [String]$Value, 168 | 169 | [Parameter(Mandatory=$false, 170 | Position=3)] 171 | [String]$Section, 172 | 173 | [Parameter(Mandatory=$false, 174 | ValueFromPipeline=$false, 175 | Position=4)] 176 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 177 | [psobject]$Encoding = "UTF8", 178 | 179 | [Parameter(Mandatory=$False, 180 | ValueFromPipeline=$False, 181 | Position=5)] 182 | [switch]$Force 183 | ) 184 | 185 | Process { 186 | if ($Section){ 187 | $Section = $Section.Replace('[','').Replace(']','') 188 | $Section = "[$Section]" 189 | } 190 | 191 | Write-Verbose "Checking if path exists" 192 | if (Test-Path $Path){ 193 | Write-Verbose "Path exists" 194 | Write-Verbose "Reading ini file with Get-Ini" 195 | $ini = Get-Ini -Path $Path 196 | Write-Verbose "Get-Ini completed" 197 | } else { 198 | Write-Error 'Path does not exist' 199 | break 200 | } 201 | 202 | if ($Section){ 203 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 204 | Write-Verbose "Checking if the section $Section already exist" 205 | if (!$availableSections.Contains($Section.tolower())) { 206 | Write-Verbose "Section does not exist" 207 | Write-Host 'Creating new section' 208 | $props = [hashtable]@{$Key = $Value} 209 | $newValue = New-Object PSObject -Property $props 210 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Section -Value $newValue 211 | } else { 212 | Write-Verbose "Section exist" 213 | [array]$availableProperties = ($ini.$Section | Get-Member -MemberType Properties | Where-Object { $_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty' } | Select-Object -ExpandProperty Name).ToLower() 214 | if (!$availableProperties.Contains($Key.ToLower())) { 215 | Write-Verbose "Property $Property exist" 216 | Write-Verbose "Setting property value to $Value" 217 | $ini.$Section | Add-Member -MemberType NoteProperty -Name $Key -Value $Value 218 | } else { 219 | Write-Verbose 'Adding property' 220 | $ini.$Section.$Key = $Value 221 | } 222 | } 223 | } else { 224 | [array]$availableProps = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 225 | if (!$availableProps.Contains($Key.ToLower())) { 226 | Write-Verbose "Property $Key does not exist" 227 | Write-Verbose "Creating new property $Key" 228 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Key -Value $Value 229 | } else { 230 | Write-Verbose "Property $Key exist" 231 | Write-Verbose "Setting property value to $Value" 232 | $ini.$Key = $Value 233 | } 234 | } 235 | 236 | Write-Verbose 'Creating ini file with New-Ini' 237 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force 238 | } 239 | } 240 | 241 | <# 242 | .Synopsis 243 | Removes a key (entire line) in ini file 244 | .DESCRIPTION 245 | Removes a key (entire line) in ini file 246 | Comments will be ignored. 247 | Warning: Even comments in the target ini file will be removed! 248 | 249 | Created by John Roos 250 | Email: john@roostech.se 251 | Web: http://blog.roostech.se 252 | .EXAMPLE 253 | Remove-IniKey -Path "c:\config.ini" -Key [system]Proxy -Encoding ASCII 254 | 255 | Opens the file config.ini and removes the key "Proxy" in the [system] section of the file. The file will be saved with ASCII encoding. 256 | .OUTPUTS 257 | Overwrites the original ini file 258 | #> 259 | function Remove-IniKey { 260 | [CmdletBinding()] 261 | param( 262 | [Parameter(Mandatory=$true, 263 | Position=0)] 264 | [string]$Path, 265 | 266 | [Parameter(Mandatory=$true, 267 | Position=1)] 268 | [string]$Key, 269 | 270 | [Parameter(Mandatory=$false, 271 | Position=2)] 272 | [string]$Section, 273 | 274 | [Parameter(Mandatory=$false, 275 | Position=3)] 276 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 277 | [string]$Encoding = "UTF8", 278 | 279 | [Parameter(Mandatory=$False, 280 | Position=4)] 281 | [switch]$Force 282 | ) 283 | 284 | Process { 285 | 286 | Write-Verbose "Checking path to ini file" 287 | if (Test-Path $Path) { 288 | Write-Verbose "Ini file found" 289 | Write-Verbose "Reading ini file with Get-Ini" 290 | $ini = Get-Ini -Path $Path -Verbose:$false 291 | Write-Verbose "Get-Ini completed" 292 | } else { 293 | Write-Error 'Cannot find ini file' 294 | break 295 | } 296 | 297 | if ($Section) { 298 | $Section = $Section.Replace('[','').Replace(']','') 299 | $inisection = "[$Section]" 300 | Write-Verbose "Key belongs to section $inisection" 301 | } else { 302 | $inisection = $null 303 | Write-Verbose 'Key does not belong to any section' 304 | } 305 | 306 | $iniproperty = $Key 307 | 308 | # check if the value contains a [section] 309 | <# 310 | if ($Value -like '*]*') { 311 | $tempvalue = $Key.Trim().Split(']') 312 | $inisection = "$($tempvalue[0].Trim())]" 313 | $iniproperty = "$($tempvalue[1].Trim())" 314 | Write-Verbose "Section: $inisection" 315 | Write-Verbose "Property: $iniproperty" 316 | } else { 317 | $inisection = "" 318 | $iniproperty = $Key 319 | Write-Verbose "No section selected" 320 | Write-Verbose "Property: $iniproperty" 321 | } 322 | #> 323 | Write-Verbose 'Key check completed' 324 | Write-Verbose 'Searching for matching keys in ini file' 325 | 326 | if ($inisection) { 327 | # if section was included in the Value parameter 328 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 329 | if ($availableSections.Contains($inisection.ToLower())) { 330 | # Section exists in the ini file 331 | Write-Verbose "Section $inisection exists" 332 | [array]$availableProps = ($ini.$inisection | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 333 | if ($availableProps.Contains($iniproperty.ToLower())) { 334 | # Property exists in the correct section in the ini file 335 | Write-Verbose "Property $iniproperty exists" 336 | $ini.$inisection.PSObject.Properties.Remove($iniproperty) 337 | Write-Verbose 'Key removed from ini object' 338 | } else { 339 | Write-Error "ini file contains the section but does not contain requested key: $iniproperty" 340 | break 341 | } 342 | } else { 343 | Write-Error "ini file does not contain requested section: $inisection" 344 | break 345 | } 346 | 347 | } else { 348 | # if no section was included in the Value parameter 349 | if ($ini.psobject.properties.name.Contains($Key)) { 350 | $ini.PSObject.Properties.Remove($Key) 351 | Write-Verbose 'Key removed from ini object' 352 | } else { 353 | Write-Error 'Key does not exist in ini file' 354 | break 355 | } 356 | 357 | } 358 | 359 | #$ini 360 | # Recreate the ini file 361 | Write-Verbose 'Saving file' 362 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 363 | } 364 | } 365 | 366 | 367 | <# 368 | .Synopsis 369 | Renames a key in ini file 370 | .DESCRIPTION 371 | Renames a key in ini file 372 | Comments will be ignored. 373 | Warning: Even comments in the target ini file will be removed! 374 | 375 | Created by John Roos 376 | Email: john@roostech.se 377 | Web: http://blog.roostech.se 378 | .EXAMPLE 379 | Rename-IniKey -Path c:\config.ini -Key Prixy -NewKey Proxy -Section system -Encoding UTF8 380 | 381 | Opens the file config.ini and renames the key "Prixy" to "Proxy" in the [system] section of the file. The file will be saved with UTF8 encoding. 382 | .OUTPUTS 383 | Overwrites the original ini file 384 | #> 385 | function Rename-IniKey { 386 | [CmdletBinding()] 387 | param( 388 | [Parameter(Mandatory=$true, 389 | Position=0)] 390 | [string]$Path, 391 | 392 | [Parameter(Mandatory=$true, 393 | Position=1)] 394 | [string]$Key, 395 | 396 | [Parameter(Mandatory=$true, 397 | Position=2)] 398 | [string]$NewKey, 399 | 400 | [Parameter(Mandatory=$false, 401 | Position=3)] 402 | [string]$Section, 403 | 404 | [Parameter(Mandatory=$false, 405 | Position=4)] 406 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 407 | [string]$Encoding = "UTF8", 408 | 409 | [Parameter(Mandatory=$False, 410 | Position=5)] 411 | [switch]$Force 412 | ) 413 | 414 | Process { 415 | 416 | Write-Verbose "Checking if path exists" 417 | if (Test-Path $Path) { 418 | Write-Verbose "Path exists" 419 | Write-Verbose "Reading ini file with Get-Ini" 420 | $ini = Get-Ini -Path $Path -Verbose:$false 421 | Write-Verbose "Get-Ini completed" 422 | } else { 423 | Write-Error 'Path does not exist' 424 | break 425 | } 426 | 427 | Write-Verbose 'Checking value to remove' 428 | 429 | if ($Section) { 430 | $Section = $Section.Replace('[','').Replace(']','') 431 | $inisection = "[$Section]" 432 | } else { 433 | $inisection = $null 434 | } 435 | 436 | $iniproperty = $Key 437 | 438 | # check if the value contains a [section] 439 | <# 440 | if ($Value -like '*]*') { 441 | $tempvalue = $Key.Trim().Split(']') 442 | $inisection = "$($tempvalue[0].Trim())]" 443 | $iniproperty = "$($tempvalue[1].Trim())" 444 | Write-Verbose "Section: $inisection" 445 | Write-Verbose "Property: $iniproperty" 446 | } else { 447 | $inisection = "" 448 | $iniproperty = $Key 449 | Write-Verbose "No section selected" 450 | Write-Verbose "Property: $iniproperty" 451 | } 452 | #> 453 | Write-Verbose 'Value check completed' 454 | Write-Verbose 'Checking object for matching properties' 455 | 456 | if ($inisection) { 457 | # if section was included in the Value parameter 458 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 459 | if ($availableSections.Contains($inisection.ToLower())) { 460 | # Section exists in the ini file 461 | Write-Verbose "Section $inisection exists" 462 | [array]$availableProps = ($ini.$inisection | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 463 | if ($availableProps.Contains($iniproperty.ToLower())) { 464 | # Property exists in the correct section in the ini file 465 | Write-Verbose "Property $iniproperty exists" 466 | $tempvalue = $ini.$inisection.$iniproperty 467 | Write-Verbose "Removing property" 468 | $ini.$inisection.PSObject.Properties.Remove($iniproperty) 469 | Write-Verbose "Recreating property with new name and same value" 470 | $ini.$inisection | Add-Member -MemberType NoteProperty -Name $NewKey -Value $tempvalue 471 | } else { 472 | Write-Error "ini file contains the section but does not contain requested configuration: $iniproperty" 473 | break 474 | } 475 | } else { 476 | Write-Error "ini file does not contain requested section: $inisection" 477 | break 478 | } 479 | 480 | } else { 481 | # if no section was included in the Value parameter 482 | if ($ini.psobject.properties.name.Contains($Key)) { 483 | $tempvalue = $ini.$iniproperty 484 | Write-Verbose "Removing property" 485 | $ini.PSObject.Properties.Remove($iniproperty) 486 | Write-Verbose "Recreating property with new name and same value" 487 | $ini | Add-Member -MemberType NoteProperty -Name $NewKey -Value $tempvalue 488 | Write-Verbose 'Value removed from ini object' 489 | 490 | } else { 491 | Write-Error 'Key does not exist in ini file' 492 | break 493 | } 494 | 495 | } 496 | 497 | #$ini 498 | # Recreate the ini file 499 | Write-Verbose 'Saving file' 500 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 501 | } 502 | } 503 | 504 | 505 | <# 506 | .Synopsis 507 | Sets a number of keys and values in ini file based on a hash table 508 | .DESCRIPTION 509 | Sets a number of keys and values in ini file based on a hash table. Sections are separated by naming them within brackets, like this: [section]key 510 | The keys will be added if they do not exist. 511 | Comments will be ignored. 512 | Warning: Even comments in the target ini file will be removed! 513 | 514 | Created by John Roos 515 | Email: john@roostech.se 516 | Web: http://blog.roostech.se 517 | .EXAMPLE 518 | Set-IniFromHash -Path c:\config.ini -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} -Encoding UTF8 519 | 520 | Opens the file config.ini and sets the key DebugLog to false and in the [settings] section sets the Hostname to localhost. 521 | .OUTPUTS 522 | Overwrites the original ini file 523 | #> 524 | function Set-IniFromHash { 525 | [CmdletBinding()] 526 | param( 527 | [Parameter(Mandatory=$true, 528 | Position=0)] 529 | [string]$Path, 530 | 531 | [Parameter(Mandatory=$true, 532 | ValueFromPipeline=$true, 533 | Position=1)] 534 | [hashtable]$Values, 535 | 536 | [Parameter(Mandatory=$false, 537 | ValueFromPipeline=$false, 538 | Position=2)] 539 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 540 | [psobject]$Encoding = "UTF8", 541 | 542 | [Parameter(Mandatory=$False, 543 | ValueFromPipeline=$False, 544 | Position=3)] 545 | [switch]$Force 546 | ) 547 | 548 | Process { 549 | [string]$section = '' 550 | 551 | Write-Verbose "Checking if path exists" 552 | if (Test-Path $Path) { 553 | Write-Verbose "File exists" 554 | $iniexist = $true 555 | Write-Verbose "Reading ini file with Get-Ini" 556 | $ini = Get-Ini -Path $Path -Verbose:$false 557 | Write-Verbose "Get-Ini completed" 558 | } else { 559 | if ($Force){ 560 | $ini = New-Object -TypeName psobject 561 | Write-Verbose 'File does not exist (new file will be created)' 562 | $iniexist = $false 563 | } else { 564 | Write-Error 'File does not exist. Use the Force parameter to create file.' 565 | break 566 | } 567 | } 568 | 569 | [array]$availableSections = @() 570 | 571 | if ($iniexist) { 572 | $availableSections = ( $ini | 573 | Get-Member -MemberType Properties | 574 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 575 | Select-Object -ExpandProperty Name | 576 | Where-Object {$_.Contains(']')} ).tolower() 577 | } 578 | 579 | foreach ($key in $Values.keys) { 580 | Write-Verbose "Processing $key" 581 | if ($key -like '*]*') { 582 | Write-Verbose "Section selected" 583 | $keysplit = $key.Split(']') 584 | $section = $keysplit[0] + ']' 585 | $property = $keysplit[1] 586 | 587 | 588 | if ($availableSections.Contains($section.ToLower())) { 589 | Write-Verbose "Section $section exists" 590 | 591 | [array]$availableProps = ( $ini.$section | 592 | Get-Member -MemberType Properties | 593 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 594 | Select-Object -ExpandProperty Name | 595 | Where-Object {!$_.Contains(']')} ).ToLower() 596 | 597 | if (!$availableProps.Contains($property.ToLower())) { 598 | Write-Verbose "Property $property does not exist" 599 | Write-Verbose "Creating new property $property" 600 | Add-Member -InputObject $ini.$section -MemberType NoteProperty -Name $property -Value $Value 601 | } else { 602 | Write-Verbose "Property $property exist" 603 | Write-Verbose "Setting property value to $Value" 604 | $ini.$section.$Property = $Value 605 | } 606 | } else { 607 | Write-Verbose "Section $section does not exist !one!" 608 | $props = [hashtable]@{$property = $Values.$key} 609 | $newValue = New-Object PSObject -Property $props 610 | $availableSections += $section.ToLower() 611 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $Section -Value $newValue 612 | } 613 | $ini.$section.$property = $Values.$key 614 | } else { 615 | Write-Verbose "No section selected" 616 | $property = $key 617 | [array]$availableProps = @() 618 | 619 | if ($iniexist) { 620 | [array]$availableProps = ($ini | 621 | Get-Member -MemberType Properties | 622 | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | 623 | Select-Object -ExpandProperty Name | 624 | Where-Object {!$_.Contains(']')}).ToLower() 625 | } 626 | 627 | if (!$availableProps.Contains($property.ToLower())) { 628 | Write-Verbose "Property $property does not exist" 629 | Write-Verbose "Creating new property $property" 630 | Add-Member -InputObject $ini -MemberType NoteProperty -Name $property -Value $Values.$key 631 | } else { 632 | Write-Verbose "Property $property exist" 633 | Write-Verbose "Setting property value to $($Values.$key)" 634 | $ini.$Property = $Values.$key 635 | } 636 | } 637 | } 638 | # uncomment below for debug 639 | # $ini 640 | Write-Verbose "Saving file $Path with encoding $Encoding" 641 | New-Ini -Path $Path -Content $ini -Encoding $Encoding -Force:$Force -Verbose:$false 642 | } 643 | } 644 | 645 | 646 | 647 | 648 | 649 | <# 650 | .Synopsis 651 | Creates an ini file based on a custom object of the type File.Ini 652 | .DESCRIPTION 653 | Creates an ini file based on a custom object of the type File.Ini 654 | Comments will be ignored. 655 | 656 | Created by John Roos 657 | Email: john@roostech.se 658 | Web: http://blog.roostech.se 659 | .EXAMPLE 660 | get-ini -Path "C:\config.ini" | new-ini -Path c:\config_new.ini -Encoding UTF8 661 | 662 | Opens the file config.ini and which creates a File.Ini object. The object is then piped to New-Ini which will create a new file based on that object. 663 | .OUTPUTS 664 | Creates a new ini file 665 | #> 666 | function New-Ini { 667 | [CmdletBinding()] 668 | param( 669 | [Parameter(Mandatory=$true, 670 | Position=0)] 671 | [string]$Path, 672 | 673 | [Parameter(Mandatory=$true, 674 | ValueFromPipeline=$true, 675 | Position=1)] 676 | [psobject]$Content, 677 | 678 | [Parameter(Mandatory=$false, 679 | ValueFromPipeline=$false, 680 | Position=2)] 681 | [ValidateSet("Unicode", "UTF7", "UTF8", "UTF32", "ASCII", "BigEndianUnicode", "Default", "OEM")] 682 | [string]$Encoding = "UTF8", 683 | 684 | [Parameter(Mandatory=$False, 685 | ValueFromPipeline=$False, 686 | Position=3)] 687 | [switch]$Force 688 | ) 689 | 690 | Process { 691 | [array]$result = @() 692 | $sections = $Content | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name 693 | foreach ($section in $sections){ 694 | if ($section -notlike '*]*') { 695 | $result += "$section=$($Content.$section)" 696 | } 697 | } 698 | if ($result.count -gt 0) { 699 | $result += " " 700 | } 701 | foreach ($section in $sections){ 702 | if ($section -like '*]*') { 703 | $result += "$section" 704 | $keys = $Content.$section | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name 705 | foreach ($key in $keys){ 706 | $result += "$key=$($Content.$section.$key)" 707 | } 708 | $result += " " 709 | } 710 | } 711 | $result | Out-File -FilePath $Path -Encoding $Encoding -Force:$Force 712 | } 713 | } 714 | 715 | 716 | <# 717 | .Synopsis 718 | Returns true or false if the ini file has the provided configuration 719 | .DESCRIPTION 720 | Reads the configuration from an ini file and compares the content with the provided hashtable. 721 | Comments will be ignored. 722 | 723 | Created by John Roos 724 | Email: john@roostech.se 725 | Web: http://blog.roostech.se 726 | .EXAMPLE 727 | Test-Ini -Path "C:\config.ini" -Values @{'DebugLog'='false';'[settings]Hostname'='localhost'} 728 | 729 | Opens the file config.ini and checks the values for 'DebugLog' (not in any section) and 'Hostname' (in section 'settubgs'). If the values are the same as the provided values the function will return True, otherwise it will return False. 730 | .OUTPUTS 731 | Boolean 732 | #> 733 | function Test-Ini { 734 | [CmdletBinding()] 735 | param( 736 | [Parameter(Mandatory=$true, 737 | Position=0)] 738 | [string]$Path, 739 | 740 | [Parameter(Mandatory=$true, 741 | ValueFromPipeline=$true, 742 | Position=1)] 743 | [hashtable]$Values 744 | ) 745 | 746 | Process { 747 | Write-Verbose "Checking if path exists" 748 | if (Test-Path $Path) { 749 | Write-Verbose "Path exists" 750 | Write-Verbose "Reading ini file with Get-Ini" 751 | $ini = Get-Ini -Path $Path -Verbose:$false 752 | Write-Verbose "Get-Ini completed" 753 | } else { 754 | Write-Error 'Path does not exist' 755 | return $false 756 | } 757 | 758 | foreach ($key in $Values.keys) { 759 | Write-Verbose "Processing $key" 760 | if ($key -like '*]*') { 761 | Write-Verbose "Section selected" 762 | $keysplit = $key.Split(']') 763 | $section = $keysplit[0] + ']' 764 | $property = $keysplit[1] 765 | [array]$availableSections = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {$_.Contains(']')}).tolower() 766 | if ($availableSections.Contains($section.ToLower())) { 767 | Write-Verbose "Section $section exists" 768 | 769 | [array]$availableProps = ($ini.$section | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 770 | if (!$availableProps.Contains($property.ToLower())) { 771 | Write-Verbose "Property $property does not exist" 772 | Write-Verbose "Property $property NOT OK (ref 1)" 773 | return $false 774 | } else { 775 | Write-Verbose "Property $property exist" 776 | if ($ini.$section.$Property -ne $Values.$key){ 777 | Write-Verbose "Property $property NOT OK (ref 2)" 778 | return $false 779 | } else { 780 | Write-Verbose "Property $property OK (ref 1)" 781 | } 782 | 783 | } 784 | } else { 785 | Write-Verbose "Section $section does not exist" 786 | return $false 787 | } 788 | $ini.$section.$property = $Values.$key 789 | } else { 790 | Write-Verbose "No section selected" 791 | $property = $key 792 | [array]$availableProps = ($ini | Get-Member -MemberType Properties | Where-Object {$_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty'} | Select-Object -ExpandProperty Name | Where-Object {!$_.Contains(']')}).ToLower() 793 | if (!$availableProps.Contains($property.ToLower())) { 794 | Write-Verbose "Property $property does not exist" 795 | Write-Verbose "Property $property NOT OK (ref 3)" 796 | return $false 797 | } else { 798 | Write-Verbose "Property $property exist" 799 | if ($ini.$Property -ne $Values.$key) { 800 | Write-Verbose "Property $property NOT OK (ref 4)" 801 | return $false 802 | } else { 803 | Write-Verbose "Property $property OK (ref 2)" 804 | } 805 | 806 | } 807 | } 808 | } 809 | 810 | return $true 811 | } 812 | } --------------------------------------------------------------------------------