PSDev.psm1
#Region '.\prefix.ps1' 0 # The content of this file will be prepended to the top of the psm1 module file. This is useful for custom module setup is needed on import. #EndRegion '.\prefix.ps1' 2 #Region '.\Private\Assert-FolderExist.ps1' 0 function Assert-FolderExist { <# .SYNOPSIS Verify and create folder .DESCRIPTION Verifies that a folder path exists, if not it will create it .PARAMETER Path Defines the path to be validated .EXAMPLE 'C:\Temp' | Assert-FolderExist This will verify that the path exists and if it does not the folder will be created #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [string] $Path ) process { $exists = Test-Path -Path $Path -PathType Container if (!$exists) { $null = New-Item -Path $Path -ItemType Directory } } } #EndRegion '.\Private\Assert-FolderExist.ps1' 31 #Region '.\Private\Invoke-GarbageCollect.ps1' 0 function Invoke-GarbageCollect { <# .SYNOPSIS Calls system.gc collect method. Purpose is mainly for readability. .DESCRIPTION Calls system.gc collect method. Purpose is mainly for readability. .EXAMPLE Invoke-GarbageCollect #> [system.gc]::Collect() } #EndRegion '.\Private\Invoke-GarbageCollect.ps1' 13 #Region '.\Private\pslog.ps1' 0 function pslog { <# .SYNOPSIS This is simple logging function that automatically log to file. Logging to console is maintained. .DESCRIPTION This is simple logging function that automatically log to file. Logging to console is maintained. .PARAMETER Severity Defines the type of log, valid vales are, Success,Info,Warning,Error,Verbose,Debug .PARAMETER Message Defines the message for the log entry .PARAMETER Source Defines a source, this is useful to separate log entries in categories for different stages of a process or for each function, defaults to default .PARAMETER Throw Specifies that when using severity error pslog will throw. This is useful in catch statements so that the terminating error is propagated upwards in the stack. .PARAMETER LogDirectoryOverride Defines a hardcoded log directory to write the log file to. This defaults to %appdatalocal%\<modulename\logs. .PARAMETER DoNotLogToConsole Specifies that logs should only be written to the log file and not to the console. .EXAMPLE pslog Verbose 'Successfully wrote to logfile' Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Sole purpose of function is logging, including console')] [cmdletbinding()] param( [parameter(Position = 0)] [ValidateSet('Success', 'Info', 'Warning', 'Error', 'Verbose', 'Debug')] [Alias('Type')] [string] $Severity, [parameter(Mandatory, Position = 1)] [string] $Message, [parameter(position = 2)] [string] $source = 'default', [parameter(Position = 3)] [switch] $Throw, [parameter(Position = 4)] [string] $LogDirectoryOverride, [parameter(Position = 5)] [switch] $DoNotLogToConsole ) begin { if (-not $LogDirectoryOverride) { $localappdatapath = [Environment]::GetFolderPath('localapplicationdata') # ie C:\Users\<username>\AppData\Local $modulename = $MyInvocation.MyCommand.Module $logdir = "$localappdatapath\$modulename\logs" } else { $logdir = $LogDirectoryOverride } $logdir | Assert-FolderExist -Verbose:$VerbosePreference $timestamp = (Get-Date) $logfilename = ('{0}.log' -f $timestamp.ToString('yyy-MM-dd')) $timestampstring = $timestamp.ToString('yyyy-MM-ddThh:mm:ss.ffffzzz') } process { switch ($Severity) { 'Success' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Host -Object "SUCCESS: $timestampstring`t$source`t$message" -ForegroundColor Green } } 'Info' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Information -MessageData "$timestampstring`t$source`t$message" } } 'Warning' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Warning -Message "$timestampstring`t$source`t$message" } } 'Error' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Error -Message "$timestampstring`t$source`t$message" } if ($throw) { throw } } 'Verbose' { if ($VerbosePreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Verbose -Message "$timestampstring`t$source`t$message" } } 'Debug' { if ($DebugPreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Debug -Message "$timestampstring`t$source`t$message" } } } } } #EndRegion '.\Private\pslog.ps1' 137 #Region '.\Private\Write-PSProgress.ps1' 0 function Write-PSProgress { <# .SYNOPSIS Wrapper for PSProgress .DESCRIPTION This function will automatically calculate items/sec, eta, time remaining as well as set the update frequency in case the there are a lot of items processing fast. .PARAMETER Activity Defines the activity name for the progressbar .PARAMETER Id Defines a unique ID for this progressbar, this is used when nesting progressbars .PARAMETER Target Defines a arbitrary text for the currently processed item .PARAMETER ParentId Defines the ID of a parent progress bar .PARAMETER Completed Explicitly tells powershell to set the progress bar as completed removing it from view. In some cases the progress bar will linger if this is not done. .PARAMETER Counter The currently processed items counter .PARAMETER Total The total number of items to process .PARAMETER StartTime Sets the start datetime for the progressbar, this is required to calculate items/sec, eta and time remaining .PARAMETER DisableDynamicUpdateFrquency Disables the dynamic update frequency function and every item will update the status of the progressbar .PARAMETER NoTimeStats Disables calculation of items/sec, eta and time remaining .EXAMPLE 1..10000 | foreach-object -begin {$StartTime = Get-Date} -process { Write-PSProgress -Activity 'Looping' -Target $PSItem -Counter $PSItem -Total 10000 -StartTime $StartTime } Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Standard')] [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Completed')] [string] $Activity, [Parameter(Position = 1, ParameterSetName = 'Standard')] [Parameter(Position = 1, ParameterSetName = 'Completed')] [ValidateRange(0, 2147483647)] [int] $Id, [Parameter(Position = 2, ParameterSetName = 'Standard')] [string] $Target, [Parameter(Position = 3, ParameterSetName = 'Standard')] [Parameter(Position = 3, ParameterSetName = 'Completed')] [ValidateRange(-1, 2147483647)] [int] $ParentId, [Parameter(Position = 4, ParameterSetname = 'Completed')] [switch] $Completed, [Parameter(Mandatory = $true, Position = 5, ParameterSetName = 'Standard')] [long] $Counter, [Parameter(Mandatory = $true, Position = 6, ParameterSetName = 'Standard')] [long] $Total, [Parameter(Position = 7, ParameterSetName = 'Standard')] [datetime] $StartTime, [Parameter(Position = 8, ParameterSetName = 'Standard')] [switch] $DisableDynamicUpdateFrquency, [Parameter(Position = 9, ParameterSetName = 'Standard')] [switch] $NoTimeStats ) # Define current timestamp $TimeStamp = (Get-Date) # Define a dynamic variable name for the global starttime variable $StartTimeVariableName = ('ProgressStartTime_{0}' -f $Activity.Replace(' ', '')) # Manage global start time variable if ($PSBoundParameters.ContainsKey('Completed') -and (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Remove the global starttime variable if the Completed switch parameter is users try { Remove-Variable -Name $StartTimeVariableName -ErrorAction Stop -Scope Global } catch { throw $_ } } elseif (-not (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Global variable do not exist, create global variable if ($null -eq $StartTime) { # No start time defined with parameter, use current timestamp as starttime Set-Variable -Name $StartTimeVariableName -Value $TimeStamp -Scope Global $StartTime = $TimeStamp } else { # Start time defined with parameter, use that value as starttime Set-Variable -Name $StartTimeVariableName -Value $StartTime -Scope Global } } else { # Global start time variable is defined, collect and use it $StartTime = Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction Stop -ValueOnly } # Define frequency threshold $Frequency = [Math]::Ceiling($Total / 100) switch ($PSCmdlet.ParameterSetName) { 'Standard' { # Only update progress is any of the following is true # - DynamicUpdateFrequency is disabled # - Counter matches a mod of defined frequecy # - Counter is 0 # - Counter is equal to Total (completed) if (($DisableDynamicUpdateFrquency) -or ($Counter % $Frequency -eq 0) -or ($Counter -eq 1) -or ($Counter -eq $Total)) { # Calculations for both timestats and without $Percent = [Math]::Round(($Counter / $Total * 100), 0) # Define count progress string status $CountProgress = ('{0}/{1}' -f $Counter, $Total) # If percent would turn out to be more than 100 due to incorrect total assignment revert back to 100% to avoid that write-progress throws if ($Percent -gt 100) { $Percent = 100 } # Define write-progress splat hash $WriteProgressSplat = @{ Activity = $Activity PercentComplete = $Percent CurrentOperation = $Target } # Add ID if specified if ($Id) { $WriteProgressSplat.Id = $Id } # Add ParentID if specified if ($ParentId) { $WriteProgressSplat.ParentId = $ParentId } # Calculations for either timestats and without if ($NoTimeStats) { $WriteProgressSplat.Status = ('{0} - {1}%' -f $CountProgress, $Percent) } else { # Total seconds elapsed since start $TotalSeconds = ($TimeStamp - $StartTime).TotalSeconds # Calculate items per sec processed (IpS) $ItemsPerSecond = ([Math]::Round(($Counter / $TotalSeconds), 2)) # Calculate seconds spent per processed item (for ETA) $SecondsPerItem = if ($Counter -eq 0) { 0 } else { ($TotalSeconds / $Counter) } # Calculate seconds remainging $SecondsRemaing = ($Total - $Counter) * $SecondsPerItem $WriteProgressSplat.SecondsRemaining = $SecondsRemaing # Calculate ETA $ETA = $(($Timestamp).AddSeconds($SecondsRemaing).ToShortTimeString()) # Add findings to write-progress splat hash $WriteProgressSplat.Status = ('{0} - {1}% - ETA: {2} - IpS {3}' -f $CountProgress, $Percent, $ETA, $ItemsPerSecond) } # Call writeprogress Write-Progress @WriteProgressSplat } } 'Completed' { Write-Progress -Activity $Activity -Id $Id -Completed } } } #EndRegion '.\Private\Write-PSProgress.ps1' 214 #Region '.\Public\Get-Office365IPURL.ps1' 0 function Get-Office365IPURL { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER Services Defines which services to retreive IP and URLs for. Valid values are Skype,Exchange,Sharepoint. Note that Teams is included in the Skype ruleset and OneDrive is included in the Sharepoint ruleset. .PARAMETER OnlyRequired Defines that only rules that are required are returned. This will exclude optional optimize rules. .PARAMETER Types Defines what type of rules to return. Valid values are URL,IP4,IP6 .PARAMETER OutputFormat Defines the output format, defaults to an array of objects. Valid values are Object and JSON as of now. If a specific format is needed for a firewall please raise a issue with the instructions for the format and it is possible to create preset for it. .PARAMETER Office365IPURL Defines the URL to the Office 365 IP URL Endpoint. Defaults to 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7'. Provided as parameter to allow queries to other environments than worldwide as well as keep agility if Microsoft would change URL. .EXAMPLE Get-Office365IPURL -Services Exchange,Skype -OnlyRequired -Types IP4,URL -Outputformat JSON #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Services', Justification = 'False positive')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Types', Justification = 'False positive')] [CmdletBinding()] param( [Parameter()] [ValidateSet('Skype', 'Exchange', 'Sharepoint')] [string[]] $Services = @('Skype', 'Exchange', 'Sharepoint'), [Parameter()] [switch] $OnlyRequired, [Parameter()] [ValidateSet('URL', 'IP4', 'IP6')] [string[]] $Types = @('URL', 'IP4', 'IP6'), [Parameter()] [ValidateSet('Object', 'JSON')] [string] $OutputFormat = 'Object', [Parameter()] [string] $Office365IPURL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7' ) $ErrorActionPreference = 'Stop' # Get latest IP URL info $Office365Endpoints = Invoke-RestMethod -Uri $Office365IPURL -Method Get # Import net module Import-Module indented.net.ip # Loop through rules $Result = $Office365Endpoints | Where-Object { $Services -contains $_.ServiceArea } | ForEach-Object { $CurrentRule = $PSItem $ObjectHash = [ordered]@{ Group = '' Service = $CurrentRule.ServiceArea Type = '' Protocol = '' Port = $null Endpoint = '' Required = $CurrentRule.Required } $CurrentRule.URLs | Where-Object { $_ -ne '' -and $_ -ne $null } | ForEach-Object { $ObjectHash.Type = 'URL' $ObjectHash.Endpoint = $PSItem $CurrentRule.TCPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'TCP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'TCP' + '_' + "$PSItem" + '_' + 'URL' [pscustomobject]$ObjectHash } $CurrentRule.UDPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'UDP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'UDP' + '_' + "$PSItem" + '_' + 'URL' [pscustomobject]$ObjectHash } } # Process IPs $CurrentRule.ips | Where-Object { $_ -ne '' -and $_ -ne $null } | ForEach-Object { if ($PSItem -like '*:*') { $ObjectHash.Type = 'IP6' } else { $ObjectHash.Type = 'IP4' } $ObjectHash.Endpoint = $PSItem $CurrentRule.TCPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'TCP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'TCP' + '_' + "$PSItem" + '_' + 'IP' [pscustomobject]$ObjectHash } $CurrentRule.UDPPorts -split (',') | Where-Object { $_ -ne '' } | ForEach-Object { $ObjectHash.Protocol = 'UDP' $ObjectHash.Port = $PSItem $ObjectHash.Group = $CurrentRule.ServiceArea + '_' + 'UDP' + '_' + "$PSItem" + '_' + 'IP' [pscustomobject]$ObjectHash } } } | Where-Object { $Types -contains $PSItem.Type } switch ($OutputFormat) { 'Object' { if ($OnlyRequired) { $Result | Where-Object { $_.required -eq $true } | Sort-Object -Property Group | Format-Table } else { $Result | Sort-Object -Property Group | Format-Table } } 'JSON' { $JSONHash = [ordered]@{} $Result | Group-Object -Property Protocol | ForEach-Object { $CurrentProtocolGroup = $PSItem # Create protocol node if it does not exist if (-not $JSONHash.Contains($CurrentProtocolGroup.Name)) { $JSONHash.Add($CurrentProtocolGroup.Name, [ordered]@{}) } $CurrentProtocolGroup.Group | Group-Object -Property Port | ForEach-Object { $CurrentPortGroup = $PSItem # Create port node if it does not exists if (-not $JSONHash.$($CurrentProtocolGroup.Name).Contains($CurrentPortGroup.Name)) { $JSONHash.$($CurrentProtocolGroup.Name).Add($CurrentPortGroup.Name, [ordered]@{}) } $CurrentPortGroup.Group | Group-Object -Property Type | ForEach-Object { $CurrentTypeGroup = $PSItem $EndpointArray = [string[]]($CurrentTypeGroup.Group.Endpoint) $JSONHash.$($CurrentProtocolGroup.Name).$($CurrentPortGroup.Name).Add($CurrentTypeGroup.Name, $EndpointArray) } } } $JSONHash | ConvertTo-Json -Depth 10 } } } #EndRegion '.\Public\Get-Office365IPURL.ps1' 167 #Region '.\Public\Test-Office365IPURL.ps1' 0 function Test-Office365IPURL { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER IP Defines the IP to search for with in the scopes of rules returned from Office 365 .PARAMETER Office365IPURL Defines the URL to the Office 365 IP URL Endpoint. Defaults to 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7'. Provided as parameter to allow queries to other environments than worldwide as well as keep agility if Microsoft would change URL. .EXAMPLE Get-Office365IPURL -Services Exchange,Skype -OnlyRequired -Types IP4,URL -Outputformat JSON #> [CmdletBinding()] param( [Parameter(Mandatory)] [string[]] $IP, [Parameter()] [string] $Office365IPURL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7' ) $ErrorActionPreference = 'Stop' # Get latest IP URL info $Office365Endpoints = Invoke-RestMethod -Uri $Office365IPURL -Method Get # Import net module Import-Module indented.net.ip # Foreach service foreach ($item in $IP) { # Foreach rule in service foreach ($rule in $Office365Endpoints) { # Select Ipv4 ips $IPv4Ranges = $rule.ips.where({ $_ -notlike '*:*' }) # Resolve IPs for URLs. There are two shortcomings of this part. First; Only the currently returned IPs are evaluated. In case other # records are returned due to GeoDNS, round robin etc those will not be known and therefor not evaluated. Second; URLs with wildcards are # not evalutated, there is no way for the script to know which URLs within the wildcard scope that will be called by services. $rule.urls | ForEach-Object { if ($_) { Resolve-DnsName $_ -ErrorAction SilentlyContinue | Where-Object { $_.GetType().Name -eq 'DnsRecord_A' } | ForEach-Object { $IPv4Ranges += $_.IPAddress } } } # Test each entry in the array if the IP is equal or belongs to the returned IP/range foreach ($range in $IPv4Ranges) { [pscustomobject]@{ RuleID = $rule.id ServiceArea = $rule.ServiceArea TCPPort = $rule.tcpPorts UDPPort = $rule.udpPorts Required = $rule.Required Range = $range Subject = $item IsMember = (Test-SubnetMember -SubjectIPAddress $item -ObjectIPAddress $range) } } } } } #EndRegion '.\Public\Test-Office365IPURL.ps1' 77 #Region '.\Public\Test-PSGalleryNameAvailability.ps1' 0 function Test-PSGalleryNameAvailability { <# .DESCRIPTION Retreive a list of ip and urls required for communication to and from Office 365. .PARAMETER PackageName Defines the package name to search for .EXAMPLE Test-PSGalleryNameAvailability -PackageName PowershellGet #> [CmdletBinding()] [OutputType([boolean])] param( [Parameter(Mandatory)] [string] $PackageName ) $Response = Invoke-WebRequest -Uri "https://www.powershellgallery.com/packages/$PackageName" -SkipHttpErrorCheck if ($Response.RawContent -like '*Page not found*') { return $true } else { return $false } } #EndRegion '.\Public\Test-PSGalleryNameAvailability.ps1' 32 #Region '.\suffix.ps1' 0 # The content of this file will be appended to the top of the psm1 module file. This is useful for custom procesedures after all module functions are loaded. #EndRegion '.\suffix.ps1' 2 |