├── scripts ├── scratch.ps1 ├── LEDTickers │ ├── SampleTicker.json │ └── StatusFunctions.ps1 ├── BiblePlan │ ├── ESVAPITest.ps1 │ ├── api.BibleTest.ps1 │ ├── outputPlan.ps1 │ └── test.ps1 ├── Remove-NonAcceptedEmailAddresses.ps1 ├── RipRow │ └── VerbalPrompts.ps1 ├── EdgeChrProfiles.ps1 ├── launchtest.ps1 ├── BibleAPIParams.ps1 ├── StaleUserManagement.md └── OneShellSetup.ps1 ├── tests ├── Import-ManagedInstallDefinition.Tests.ps1 ├── Pester.Tests.ps1 ├── ScriptAnalyzer.Tests.ps1 ├── Module.Tests.ps1 ├── Help.Tests.ps1 └── Get-RandomPassword.Tests.ps1 ├── docs ├── image.png ├── Get-DateSeries.gif └── MinimumRolesForMicrosoftTenantConsultant.md ├── functions ├── AD │ ├── Get-ADDrive.ps1 │ ├── Get-AdObjectDomain.ps1 │ ├── Get-ADAttributeRangeUpper.ps1 │ ├── Get-ADDomainNetBiosName.ps1 │ ├── Get-QualifiedADUserObject.ps1 │ ├── Get-ADRecipientObject.ps1 │ ├── Get-ADAttributeSchema.ps1 │ ├── Get-XADUserPasswordExpirationDate.ps1 │ └── Enable-AzureADPIMRole.ps1 ├── Utilities │ ├── Get-PSModulePath.ps1 │ ├── Clear-Terminal.ps1 │ ├── Get-CommonParameter.ps1 │ ├── Start-WindowsSecurity.ps1 │ ├── Reset-NetAdapter.ps1 │ ├── Get-JoinedPath.ps1 │ ├── Start-KeepAlive.ps1 │ ├── Get-AlphaNumeric.ps1 │ ├── Get-CustomRange.ps1 │ ├── Set-DynamicParameterVariable.ps1 │ ├── Set-VirtualizationNetAdapterToPrivate.ps1 │ ├── Invoke-SessionAs.ps1 │ ├── Get-AllParameter.ps1 │ ├── Get-RandomFileName.ps1 │ ├── Update-ExistingObjectFromMultivaluedAttribute.ps1 │ ├── Get-PSRHistory.ps1 │ ├── Get-FileChecksum.ps1 │ ├── Repair-PowerShellGet.ps1 │ ├── Export-Credential.ps1 │ ├── Get-RandomPassword.ps1 │ ├── Show-One.ps1 │ ├── Send-IFTTTRichNotification.ps1 │ ├── Out-FunctionFile.ps1 │ ├── Export-JSON.ps1 │ ├── Remove-ExistingObjectFromMultivaluedAttribute.ps1 │ ├── Get-ArrayIndexForValue.ps1 │ ├── Get-EmptyDirectory.ps1 │ ├── Compare-ColumnMapping.ps1 │ ├── Get-AllParametersWithAValue.ps1 │ ├── Remove-Member.ps1 │ ├── Convert-StringBoolToBool.ps1 │ ├── Get-SelfSignedCert.ps1 │ ├── Get-RemoteCertificate.ps1 │ ├── Import-JSON.ps1 │ ├── Test-IsWindowsTerminal.ps1 │ ├── Convert-HashtableToObject.ps1 │ ├── New-GenericList.ps1 │ ├── ConvertTo-MarkDown.ps1 │ ├── Convert-SecureStringToString.ps1 │ ├── Get-AvailableExceptionsList.ps1 │ ├── Group-Join.ps1 │ ├── Get-StringHash.ps1 │ ├── Get-UNCPath.ps1 │ ├── Measure-Property.ps1 │ ├── Get-SpecialFolder.ps1 │ ├── Get-MaxLengthOfAllAttributes.ps1 │ ├── Add-MediaFileDateTakenAttribute.ps1 │ ├── Start-ComplexJob.ps1 │ ├── Add-FunctionToPSSession.ps1 │ ├── Out-FileUtf8NoBom.ps1 │ ├── Get-FunctionInfo.ps1 │ ├── Get-CSVExportPropertySet.ps1 │ └── Get-UniqueIPsFromTextFiles.ps1 ├── Exchange │ ├── ExchangeProxyFunctionsPOC1.ps1 │ ├── Test-EmailAddress.ps1 │ ├── Add-ExchangeAliasToTestExchangeAlias.ps1 │ ├── Find-PrimarySMTPAddress.ps1 │ ├── Add-ExchangeProxyAddressToTestExchangeProxyAddress.ps1 │ ├── Test-ExchangeCommandExists.ps1 │ ├── Remove-EmailAddressFromMailbox.ps1 │ ├── Add-EmailAddressToMailbox.ps1 │ ├── New-TestExchangeAlias.ps1 │ ├── Test-ExchangeAlias.ps1 │ ├── Get-SortableSizeValue.ps1 │ ├── New-TestExchangeProxyAddress.ps1 │ ├── Get-DuplicateEmailAddresses.ps1 │ ├── Test-ExchangeProxyAddress.ps1 │ └── Test-RecipientObjectForUnwantedSMTPAddresses.ps1 ├── DateTime │ ├── Get-DateStamp.ps1 │ ├── New-TimedExcerciseTimer.ps1 │ ├── New-BackgroundTimer.ps1 │ ├── Get-TimeInZone.ps1 │ └── New-PomoTimer.ps1 ├── ADSync │ ├── Get-ByteArrayFromGUID.ps1 │ ├── Get-GUIDFromImmutableID.ps1 │ ├── Get-ImmutableIDFromGUID.ps1 │ ├── Get-GuidFromByteArray.ps1 │ └── Export-ADSyncConnectorChanges.ps1 ├── TestFunctions │ ├── Test-CommandIsPresent.ps1 │ ├── Test-IsNullOrWhiteSpace.ps1 │ ├── Test-IsNotNullOrWhiteSpace.ps1 │ ├── Test-CurrentPrincipalIsAdmin.ps1 │ ├── Test-StringIsConvertibleToGUID.ps1 │ ├── Test-IP.ps1 │ ├── Test-ForImportedModule.ps1 │ ├── Test-DirectoryPath.ps1 │ ├── Test-ForInstalledModule.ps1 │ ├── Test-EmailAddress.ps1 │ ├── Test-ADPsDrive.ps1 │ ├── Test-IsWriteableDirectory.ps1 │ ├── Test-Member.ps1 │ ├── Test-DirectorySynchronization.ps1 │ └── Test-TCPConnection.ps1 ├── OneDrive │ ├── New-OneDriveQuickAccess.ps1 │ └── Get-OneDriveAccount.ps1 ├── UserInput │ ├── Read-FolderBrowserDialog.ps1 │ ├── Read-OpenFileDialog.ps1 │ └── Read-InputBoxDialog.ps1 ├── ManagedInstalls │ ├── Get-LocalGitRepo.ps1 │ └── Update-LocalGitRepo.ps1 ├── Windows │ ├── Start-SelectedApp.ps1 │ └── Search-FileIndex.ps1 ├── Word │ └── New-WordTable.ps1 ├── MSOL │ ├── Get-MsolUserLicenseDetail.ps1 │ └── Enable-AzureADPIMRole.ps1 └── Outlook │ └── Send-OutlookMail.ps1 ├── license ├── dev ├── Get-ExchangeRecipient.ps1 └── NewFunctions.ps1 ├── .vscode └── launch.json ├── Profile.psm1 ├── promptThemes └── MC.psm1 └── Profile.psd1 /scripts/scratch.ps1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Import-ManagedInstallDefinition.Tests.ps1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exactmike/Profile/HEAD/docs/image.png -------------------------------------------------------------------------------- /docs/Get-DateSeries.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exactmike/Profile/HEAD/docs/Get-DateSeries.gif -------------------------------------------------------------------------------- /docs/MinimumRolesForMicrosoftTenantConsultant.md: -------------------------------------------------------------------------------- 1 | # Entra ID Roles 2 | 3 | - Application Developer 4 | - Directory Reader -------------------------------------------------------------------------------- /functions/AD/Get-ADDrive.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ADDrive { 2 | get-psdrive -PSProvider ActiveDirectory 3 | } 4 | -------------------------------------------------------------------------------- /functions/Utilities/Get-PSModulePath.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PSModulePath { 2 | 3 | $env:PSModulePath.split(';') 4 | 5 | } 6 | -------------------------------------------------------------------------------- /functions/Exchange/ExchangeProxyFunctionsPOC1.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exactmike/Profile/HEAD/functions/Exchange/ExchangeProxyFunctionsPOC1.ps1 -------------------------------------------------------------------------------- /functions/Utilities/Clear-Terminal.ps1: -------------------------------------------------------------------------------- 1 | function Clear-Terminal 2 | { 3 | Clear-Host 4 | Write-Output "$([char]27)[2J$([char]27)[3J" 5 | } 6 | -------------------------------------------------------------------------------- /functions/DateTime/Get-DateStamp.ps1: -------------------------------------------------------------------------------- 1 | Function Get-DateStamp { 2 | 3 | [string]$Stamp = Get-Date -Format yyyyMMdd 4 | $Stamp 5 | 6 | } 7 | -------------------------------------------------------------------------------- /functions/Utilities/Get-CommonParameter.ps1: -------------------------------------------------------------------------------- 1 | Function Get-CommonParameter { 2 | 3 | [cmdletbinding()] 4 | param() 5 | $MyInvocation.MyCommand.Parameters.Keys 6 | 7 | } 8 | -------------------------------------------------------------------------------- /functions/ADSync/Get-ByteArrayFromGUID.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ByteArrayFromGUID { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [guid]$GUID 7 | ) 8 | $GUID.ToByteArray() 9 | 10 | } 11 | -------------------------------------------------------------------------------- /functions/Utilities/Start-WindowsSecurity.ps1: -------------------------------------------------------------------------------- 1 | Function Start-WindowsSecurity { 2 | 3 | #useful in RDP sessions especially on Windows 2012 4 | (New-Object -ComObject Shell.Application).WindowsSecurity() 5 | 6 | } 7 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-CommandIsPresent.ps1: -------------------------------------------------------------------------------- 1 | Function Test-CommandIsPresent { 2 | 3 | Param ([string]$command) 4 | Try {if (Get-Command -Name $command -ErrorAction Stop) {$true}} 5 | Catch {$false} 6 | 7 | } 8 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-IsNullOrWhiteSpace.ps1: -------------------------------------------------------------------------------- 1 | Function Test-IsNullOrWhiteSpace { 2 | 3 | [cmdletbinding()] 4 | Param 5 | ( 6 | $String 7 | ) 8 | [string]::IsNullOrWhiteSpace($String) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /functions/ADSync/Get-GUIDFromImmutableID.ps1: -------------------------------------------------------------------------------- 1 | Function Get-GUIDFromImmutableID { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | $ImmutableID 7 | ) 8 | [GUID][convert]::frombase64string($ImmutableID) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /functions/ADSync/Get-ImmutableIDFromGUID.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ImmutableIDFromGUID { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [guid]$Guid 7 | ) 8 | [Convert]::ToBase64String($Guid.ToByteArray()) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-IsNotNullOrWhiteSpace.ps1: -------------------------------------------------------------------------------- 1 | Function Test-IsNotNullOrWhiteSpace { 2 | 3 | [cmdletbinding()] 4 | Param( 5 | $String 6 | ) 7 | [string]::IsNullOrWhiteSpace($String) -eq $false 8 | 9 | } 10 | -------------------------------------------------------------------------------- /functions/DateTime/New-TimedExcerciseTimer.ps1: -------------------------------------------------------------------------------- 1 | Function New-TimedExcerciseTimer { 2 | 3 | New-Timer -units Minutes -length 3 -voice -showprogress -Frequency .25 -altReport @{Units = 'Seconds'; Frequency = 1; Countdownpoint = 10} -delay 5 4 | 5 | } 6 | -------------------------------------------------------------------------------- /functions/Utilities/Reset-NetAdapter.ps1: -------------------------------------------------------------------------------- 1 | function Reset-NetAdapter 2 | { 3 | param( 4 | [parameter()] 5 | [validateSet('Wi-Fi')] 6 | $Name 7 | ) 8 | 9 | Get-NetAdapter -Name $Name | Disable-NetAdapter -passthru -confirm:$false | Enable-NetAdapter 10 | } -------------------------------------------------------------------------------- /functions/ADSync/Get-GuidFromByteArray.ps1: -------------------------------------------------------------------------------- 1 | Function Get-GuidFromByteArray { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [byte[]]$GuidByteArray 7 | ) 8 | New-Object -TypeName guid -ArgumentList (, $GuidByteArray) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /functions/Utilities/Get-JoinedPath.ps1: -------------------------------------------------------------------------------- 1 | function Get-JoinedPath 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [string[]]$Path 6 | ) 7 | foreach ($p in $Path) 8 | { 9 | if ($p -contains ':' -and $p -notmatch ''){} 10 | } 11 | [IO.Path]::Combine($path) 12 | } 13 | -------------------------------------------------------------------------------- /scripts/LEDTickers/SampleTicker.json: -------------------------------------------------------------------------------- 1 | [{"text":"Size 1","color":"FF0000","size":1,"x":31,"y":4,"align":"C"},{"text":"Size 2","color":"00FF00","size":2,"x":1,"y":15,"align":"L"},{"text":"Size 3","color":"0000FF","size":3,"x":62,"y":15,"align":"R"},{"text":"Size 4","color":"FFFFFF","size":4,"x":1,"y":30,"align":"L"}] -------------------------------------------------------------------------------- /functions/Utilities/Start-KeepAlive.ps1: -------------------------------------------------------------------------------- 1 | function Start-KeepAlive 2 | { 3 | $wshell = New-Object -ComObject wscript.shell 4 | do 5 | { 6 | $wshell.SendKeys('{F13}') 7 | Write-Host 'Sleeping 60 Seconds' 8 | Start-Sleep -Seconds 60 9 | } 10 | until ($false) 11 | } 12 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-CurrentPrincipalIsAdmin.ps1: -------------------------------------------------------------------------------- 1 | Function Test-CurrentPrincipalIsAdmin 2 | { 3 | 4 | $currentPrincipal = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()) 5 | $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator') 6 | 7 | } 8 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-StringIsConvertibleToGUID.ps1: -------------------------------------------------------------------------------- 1 | Function Test-StringIsConvertibleToGUID { 2 | 3 | [CmdletBinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline)] 7 | [String]$string 8 | ) 9 | try {([guid]$string -is [guid])} catch {$false} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-IP.ps1: -------------------------------------------------------------------------------- 1 | Function Test-IP { 2 | 3 | #https://gallery.technet.microsoft.com/scriptcenter/A-short-tip-to-validate-IP-4f039260 4 | param 5 | ( 6 | [Parameter(Mandatory)] 7 | [ValidateScript( {$_ -match [IPAddress]$_})] 8 | [String]$ip 9 | ) 10 | $ip 11 | 12 | } 13 | -------------------------------------------------------------------------------- /functions/Utilities/Get-AlphaNumeric.ps1: -------------------------------------------------------------------------------- 1 | Function Get-AlphaNumeric { 2 | 3 | $numbers = 1..26 4 | $letters = Get-CustomRange -first a -second z -type char 5 | foreach ($n in $numbers) 6 | { 7 | [PSCustomObject]@{ 8 | Letter = $letters[($n-1)] 9 | Number = $n 10 | } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /scripts/BiblePlan/ESVAPITest.ps1: -------------------------------------------------------------------------------- 1 | #param( 2 | $passage = 'Proverbs+14' 3 | #) 4 | 5 | $myAPIKey = $APIKey 6 | $headers = @{ 7 | Authorization = "Token $myAPIKey" 8 | } 9 | $AudioEndpoint = 'https://api.esv.org/v3/passage/audio/' 10 | $query = '?q=' + $passage 11 | $uri= $AudioEndpoint + $query 12 | 13 | Invoke-RestMethod -FollowRelLink -Uri $uri -Method Get -Headers $headers -OutFile test.mp3 14 | -------------------------------------------------------------------------------- /functions/OneDrive/New-OneDriveQuickAccess.ps1: -------------------------------------------------------------------------------- 1 | function New-OneDriveQuickAccess 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory,ValueFromPipeline)] 6 | [psobject]$OneDriveAccount 7 | , 8 | [parameter(Mandatory)] 9 | [string]$NickName 10 | ) 11 | Set-ItemProperty -path $OneDriveAccount.RegistryPath -name 'NickName' -Value $NickName 12 | } -------------------------------------------------------------------------------- /scripts/BiblePlan/api.BibleTest.ps1: -------------------------------------------------------------------------------- 1 | #param( 2 | $passage = 'Proverbs+14' 3 | #) 4 | 5 | $myAPIKey = $apiBibleKey 6 | $headers = @{ 7 | 'api-key' = $myAPIKey 8 | } 9 | 10 | $baseURL = 'https://api.scripture.api.bible/v1/' 11 | $Bibles = 'bibles' 12 | $query = '?language=eng&include-full-details=false' 13 | 14 | $uri= $baseURL + $Bibles + $query 15 | 16 | Invoke-RestMethod -FollowRelLink -Uri $uri -Method Get -Headers $headers -------------------------------------------------------------------------------- /functions/TestFunctions/Test-ForImportedModule.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ForImportedModule { 2 | 3 | Param( 4 | [parameter(Mandatory = $True)] 5 | [string]$Name 6 | ) 7 | If 8 | ( 9 | (Get-Module -Name $Name -ErrorAction SilentlyContinue) ` 10 | -or (Get-PSSnapin -Name $Name -Registered -ErrorAction SilentlyContinue) 11 | ) 12 | {$True} 13 | Else 14 | {$False} 15 | 16 | } 17 | -------------------------------------------------------------------------------- /scripts/Remove-NonAcceptedEmailAddresses.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param( 3 | [parameter(Mandatory)] 4 | [validatescript( { Test-Path -Path $_ -type Leaf })] 5 | $InputFilePath 6 | ) 7 | 8 | $MailboxesToModify = @(Import-Csv -Path $InputFilePath) 9 | 10 | foreach ($m in $MailboxesToModify) 11 | { 12 | $RemoveAddresses = $m.NonAcceptedEmails.split(';') 13 | Set-Mailbox -Identity $m.ExchangeGUID -EmailAddresses @{remove = $RemoveAddresses } 14 | } -------------------------------------------------------------------------------- /functions/Utilities/Get-CustomRange.ps1: -------------------------------------------------------------------------------- 1 | Function Get-CustomRange { 2 | 3 | #http://www.vistax64.com/powershell/15525-range-operator.html 4 | [cmdletbinding()] 5 | param( 6 | [string] $first 7 | , 8 | [string] $second 9 | , 10 | [string] $type 11 | ) 12 | $rangeStart = [int] ($first -as $type) 13 | $rangeEnd = [int] ($second -as $type) 14 | $rangeStart..$rangeEnd | ForEach-Object { $_ -as $type } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-DirectoryPath.ps1: -------------------------------------------------------------------------------- 1 | Function Test-DirectoryPath { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory = $true)] 6 | [string]$path 7 | ) 8 | if (Test-Path -Path $path -PathType Container) 9 | { 10 | $item = Get-Item -Path $path 11 | if ($item.GetType().fullname -eq 'System.IO.DirectoryInfo') 12 | {$true} 13 | else 14 | {$false} 15 | } 16 | else 17 | {$false} 18 | 19 | } 20 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-ForInstalledModule.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ForInstalledModule { 2 | 3 | Param 4 | ( 5 | [parameter(Mandatory = $True)] 6 | [string]$Name 7 | ) 8 | If 9 | ( 10 | (Get-Module -Name $Name -ListAvailable -ErrorAction SilentlyContinue) ` 11 | -or (Get-PSSnapin -Name $Name -ErrorAction SilentlyContinue) ` 12 | -or (Get-PSSnapin -Name $Name -Registered -ErrorAction SilentlyContinue) 13 | ) 14 | {$True} 15 | Else 16 | {$False} 17 | 18 | } 19 | -------------------------------------------------------------------------------- /functions/Utilities/Set-DynamicParameterVariable.ps1: -------------------------------------------------------------------------------- 1 | Function Set-DynamicParameterVariable { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory)] 7 | [System.Management.Automation.RuntimeDefinedParameterDictionary]$dictionary 8 | ) 9 | foreach ($p in $Dictionary.Keys) 10 | { 11 | Set-Variable -Name $p -Value $Dictionary.$p.value -Scope 1 12 | #Write-Verbose "Adding/Setting variable for dynamic parameter '$p' with value '$($PSBoundParameters.$p)'" 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-EmailAddress.ps1: -------------------------------------------------------------------------------- 1 | Function Test-EmailAddress 2 | { 3 | 4 | [cmdletbinding()] 5 | param 6 | ( 7 | [parameter(Mandatory, ValueFromPipeline)] 8 | [string[]]$EmailAddress 9 | ) 10 | process 11 | { 12 | foreach ($ea in $EmailAddress) 13 | { 14 | #Regex borrowed from: http://www.regular-expressions.info/email.html 15 | $ea -imatch '^(?=[A-Z0-9][A-Z0-9@._%+-]{5,253}$)[A-Z0-9._%+-]{1,64}@(?:(?=[A-Z0-9-]{1,63}\.)[A-Z0-9]+(?:-[A-Z0-9]+)*\.){1,8}[A-Z]{2,63}$' 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /functions/Exchange/Test-EmailAddress.ps1: -------------------------------------------------------------------------------- 1 | Function Test-EmailAddress { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline)] 7 | [string[]]$EmailAddress 8 | ) 9 | process 10 | { 11 | foreach ($ea in $EmailAddress) 12 | { 13 | #Regex borrowed from: http://www.regular-expressions.info/email.html 14 | $ea -imatch '^(?=[A-Z0-9][A-Z0-9@._%+-]{5,253}$)[A-Z0-9._%+-]{1,64}@(?:(?=[A-Z0-9-]{1,63}\.)[A-Z0-9]+(?:-[A-Z0-9]+)*\.){1,8}[A-Z]{2,63}$' 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /functions/Utilities/Set-VirtualizationNetAdapterToPrivate.ps1: -------------------------------------------------------------------------------- 1 | Function Set-VirtualizationNetAdapterToPrivate { 2 | 3 | param ( 4 | $Name = 'Unidentified network' 5 | , 6 | $InterfaceAliasPrefix = 'vEthernet' 7 | ) 8 | $MatchingConnectionProfiles = @(Get-NetConnectionProfile -InterfaceAlias "$InterfaceAliasPrefix*" -Name $Name -ErrorAction SilentlyContinue) 9 | $MatchingConnectionProfiles | Where-Object {$_.IPv4Connectivity -eq 'NoTraffic' -and $_.IPv6Connectivity -eq 'NoTraffic'} | Set-NetConnectionProfile -NetworkCategory Private 10 | 11 | } 12 | -------------------------------------------------------------------------------- /functions/Exchange/Add-ExchangeAliasToTestExchangeAlias.ps1: -------------------------------------------------------------------------------- 1 | Function Add-ExchangeAliasToTestExchangeAlias { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [string]$Alias 7 | , 8 | [string[]]$ObjectGUID #should be the AD ObjectGuid 9 | ) 10 | if ($Script:TestExchangeAlias.ContainsKey($alias)) 11 | { 12 | throw("Alias $Alias already exists in the TestExchangeAlias Table") 13 | } 14 | else 15 | { 16 | $Script:TestExchangeAlias.$alias = @() 17 | $Script:TestExchangeAlias.$alias += $ObjectGUID 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /scripts/BiblePlan/outputPlan.ps1: -------------------------------------------------------------------------------- 1 | $d = 0 2 | $ntresult.foreach({ 3 | $d++ 4 | [pscustomobject]@{ 5 | Day = $d 6 | Readings = @($NTC[$_.start..$_.end] | Group-Object -Property Book).foreach({ 7 | switch ($_) 8 | { 9 | {$_.group.count -eq 1} 10 | {"$($_.Name)" + " $($_.group.chapter[0])" } 11 | {$_.group.count -gt 1} 12 | { 13 | "$($_.Name)" + " $($_.group.chapter[0])" + '-' + "$($_.group.chapter[-1])" 14 | } 15 | } 16 | }) -join ';' 17 | } 18 | }) -------------------------------------------------------------------------------- /functions/Utilities/Invoke-SessionAs.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-SessionAS 2 | { 3 | param( 4 | [parameter(Mandatory)] 5 | [string]$Identity 6 | , 7 | [switch]$SaveCredential 8 | , 9 | [parameter()] 10 | [ValidateSet('PWSH', 'Powershell', 'CMD')] 11 | [string]$CLI = 'PWSH' 12 | ) 13 | switch ($SaveCredential) 14 | { 15 | $true 16 | { 17 | runas /savecred /user:$Identity "$CLI -noexit" 18 | } 19 | $false 20 | { 21 | runas /user:$Identity "$CLI -noexit" 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /functions/Utilities/Get-AllParameter.ps1: -------------------------------------------------------------------------------- 1 | Function Get-AllParameter { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | $BoundParameters #$PSBoundParameters 7 | , 8 | $AllParameters #$MyInvocation.MyCommand.Parameters 9 | , 10 | [switch]$IncludeCommon 11 | ) 12 | $AllKeys = $($AllParameters.Keys ; $BoundParameters.Keys) 13 | $AllKeys = $AllKeys | Sort-Object -Unique 14 | if ($IncludeCommon -ne $true) 15 | { 16 | $AllKeys = $AllKeys | Where-Object -FilterScript {$_ -notin @(Get-CommonParameter)} 17 | } 18 | $AllKeys 19 | 20 | } 21 | -------------------------------------------------------------------------------- /functions/Utilities/Get-RandomFileName.ps1: -------------------------------------------------------------------------------- 1 | Function Get-RandomFileName 2 | { 3 | [cmdletbinding(DefaultParameterSetName = 'WithExtension')] 4 | param ( 5 | [parameter(Mandatory, ParameterSetName = 'SpecifiedExtension')] 6 | [string]$extension 7 | ) 8 | 9 | switch ($PSCmdlet.ParameterSetName) 10 | { 11 | 'WithExtension' 12 | { 13 | ([IO.Path]::GetRandomFileName()) 14 | } 15 | 'SpecifiedExtension' 16 | { 17 | "$([io.path]::GetFileNameWithoutExtension([IO.Path]::GetRandomFileName())).$extension" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /functions/Utilities/Update-ExistingObjectFromMultivaluedAttribute.ps1: -------------------------------------------------------------------------------- 1 | Function Update-ExistingObjectFromMultivaluedAttribute { 2 | 3 | [CmdletBinding()] 4 | param 5 | ( 6 | $ParentObject 7 | , 8 | $ChildObject 9 | , 10 | $MultiValuedAttributeName 11 | , 12 | $IdentityAttributeName 13 | ) 14 | $index = Get-ArrayIndexForValue -array $ParentObject.$MultiValuedAttributeName -value $ChildObject.$IdentityAttributeName -property $IdentityAttributeName 15 | $ParentObject.$MultiValuedAttributeName[$index] = $ChildObject 16 | $ParentObject 17 | 18 | } 19 | -------------------------------------------------------------------------------- /functions/Utilities/Get-PSRHistory.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PSRHistory 2 | { 3 | [CmdletBinding()] 4 | [outputtype([string[]])] 5 | param( 6 | [parameter(Position = 1)] 7 | [string]$SimpleMatch 8 | ) 9 | 10 | function Get-RawPSReadLineHistory 11 | { 12 | Get-Content (Get-PSReadLineOption).HistorySavePath 13 | } 14 | switch ([string]::IsNullOrEmpty($SimpleMatch)) 15 | { 16 | $true 17 | { 18 | Get-RawPSReadLineHistory 19 | } 20 | $false 21 | { 22 | Get-RawPSReadLineHistory | Select-String -SimpleMatch $SimpleMatch 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /functions/Utilities/Get-FileChecksum.ps1: -------------------------------------------------------------------------------- 1 | Function Get-FileChecksum 2 | { 3 | 4 | Param ( 5 | [parameter(Mandatory = $True)] 6 | [ValidateScript( { Test-Path -path $_ -PathType Leaf })] 7 | [string]$Path 8 | , 9 | [ValidateSet('sha1', 'md5', 'sha512','sha256')] 10 | [string]$Algorithm 11 | ) 12 | $FileObject = Get-Item -Path $Path 13 | $fs = [System.IO.FileStream]::new($($FileObject.FullName), 'Open' ) 14 | $algo = [type]"System.Security.Cryptography.$Algorithm" 15 | $crypto = $algo::Create() 16 | $hash = [BitConverter]::ToString($crypto.ComputeHash($fs)).Replace('-', '') 17 | $fs.Close() 18 | $hash 19 | 20 | } 21 | -------------------------------------------------------------------------------- /functions/Utilities/Repair-PowerShellGet.ps1: -------------------------------------------------------------------------------- 1 | function Repair-PowerShellGet 2 | { 3 | param( 4 | [switch]$FixSecurityProtocol 5 | ) 6 | 7 | switch($FixSecurityProtocol) 8 | { 9 | $true 10 | { 11 | #ideally add this to all users and shells profile if not set on the machine globally 12 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 13 | } 14 | } 15 | 16 | Install-PackageProvider -Name NuGet -Force -Scope AllUsers 17 | Install-Module -Name PowerShellGet -Force -AllowClobber -Scope AllUsers 18 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 19 | } -------------------------------------------------------------------------------- /functions/Utilities/Export-Credential.ps1: -------------------------------------------------------------------------------- 1 | Function Export-Credential { 2 | 3 | param( 4 | [string]$message 5 | , 6 | [string]$username 7 | ) 8 | $GetCredentialParams = @{} 9 | if ($message) {$GetCredentialParams.Message = $message} 10 | if ($username) {$GetCredentialParams.Username = $username} 11 | 12 | $credential = Get-Credential @GetCredentialParams 13 | 14 | $ExportUserName = $credential.UserName 15 | $ExportPassword = ConvertFrom-SecureString -Securestring $credential.Password 16 | 17 | $exportCredential = [pscustomobject]@{ 18 | UserName = $ExportUserName 19 | Password = $ExportPassword 20 | } 21 | $exportCredential 22 | 23 | } 24 | -------------------------------------------------------------------------------- /functions/Utilities/Get-RandomPassword.ps1: -------------------------------------------------------------------------------- 1 | Function Get-RandomPassword 2 | { 3 | [cmdletbinding()] 4 | Param 5 | ( 6 | [parameter()] 7 | [int]$Length = 16 8 | , 9 | [parameter()] 10 | [validateset('Any', 'NoSpecial')] 11 | [string]$CharacterSet = 'Any' 12 | ) 13 | 14 | $ArrayOfChars = [char[]]@( 15 | switch ($CharacterSet) 16 | { 17 | 'Any' 18 | {([char]33..[char]95) + ([char[]]([char]97..[char]126))} 19 | 'NoSpecial' 20 | {([char]48..[char]57) + ([char]65..[char]90) + ([char[]]([char]97..[char]122))} 21 | } 22 | ) 23 | (1..$Length | ForEach-Object {$ArrayOfChars | Get-Random}) -join '' 24 | } 25 | -------------------------------------------------------------------------------- /functions/Utilities/Show-One.ps1: -------------------------------------------------------------------------------- 1 | Function Show-One { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(ValueFromPipeline)] 7 | [psobject[]]$PSObject 8 | , 9 | [switch]$ClearHost 10 | ) 11 | process 12 | { 13 | foreach ($o in $PSObject) 14 | { 15 | if ($true -eq $quit) 16 | { 17 | break 18 | } 19 | if ($true -eq $Clearhost) {Clear-Host} 20 | $o | Format-List -Property * -Force 21 | if ($(Read-Host -Prompt "Show next object (any key), or quit (q)?") -eq 'q') 22 | { 23 | $quit = $true 24 | } 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /functions/Exchange/Find-PrimarySMTPAddress.ps1: -------------------------------------------------------------------------------- 1 | Function Find-PrimarySMTPAddress { 2 | 3 | [cmdletbinding()] 4 | Param 5 | ( 6 | [parameter(mandatory = $true)] 7 | [Alias('EmailAddresses')] 8 | [string[]]$ProxyAddresses 9 | ) 10 | $PrimaryAddresses = @($ProxyAddresses | Where-Object {$_ -clike 'SMTP:*'} | ForEach-Object {($_ -split ':')[1]}) 11 | switch ($PrimaryAddresses.count) 12 | { 13 | 1 14 | { 15 | $PrimarySMTPAddress = $PrimaryAddresses[0] 16 | $PrimarySMTPAddress 17 | }#1 18 | 0 19 | { 20 | $null 21 | }#0 22 | Default 23 | { 24 | $false 25 | }#Default 26 | }#switch 27 | 28 | } 29 | -------------------------------------------------------------------------------- /functions/Utilities/Send-IFTTTRichNotification.ps1: -------------------------------------------------------------------------------- 1 | function Send-IftttRichNotification 2 | { 3 | #Attribution: https://www.dennisrye.com/post/send-smartphone-notifications-powershell-ifttt/ 4 | [cmdletbinding()] 5 | param( 6 | [Parameter(Mandatory)] 7 | [string]$EventName, 8 | 9 | [Parameter(Mandatory)] 10 | [string]$Key, 11 | 12 | [string]$Value1, 13 | 14 | [string]$Value2, 15 | 16 | [string]$Value3 17 | ) 18 | 19 | $webhookUrl = "https://maker.ifttt.com/trigger/{0}/with/key/{1}" -f $EventName, $Key 20 | 21 | $body = @{ 22 | value1 = $Value1 23 | value2 = $Value2 24 | value3 = $Value3 25 | } 26 | 27 | Invoke-RestMethod -Method Get -Uri $webhookUrl -Body $body 28 | } -------------------------------------------------------------------------------- /functions/Utilities/Out-FunctionFile.ps1: -------------------------------------------------------------------------------- 1 | function Out-FunctionFile 2 | { 3 | [CmdletBinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline)] 7 | [System.Management.Automation.FunctionInfo]$FunctionInfo 8 | , 9 | [Parameter(Mandatory)] 10 | [string]$Path 11 | ) 12 | process 13 | { 14 | foreach ($Function in $FunctionInfo) 15 | { 16 | $FileName = $Function.Name + '.ps1' 17 | $Contents = 18 | @" 19 | Function $($Function.Name) 20 | { 21 | $($Function.Scriptblock) 22 | } 23 | 24 | "@ 25 | $outpath = Join-Path -Path $Path -ChildPath $FileName 26 | $Contents | Out-File -FilePath $outpath -Encoding utf8 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /functions/Utilities/Export-JSON.ps1: -------------------------------------------------------------------------------- 1 | Function Export-JSON 2 | { 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipelineByPropertyName)] 7 | [Alias('PSPath', 'FullName')] 8 | [string]$FilePath 9 | , 10 | [parameter()] 11 | [validateSet('Unicode', 'UTF7', 'UTF8', 'ASCII', 'UTF32', 'BigEndianUnicode', 'Default', 'OEM')] 12 | $Encoding 13 | , 14 | [parameter(ValueFromPipeline)] 15 | [psobject]$InputObject 16 | , 17 | [switch]$Compress 18 | ) 19 | 20 | $ConvertParams = @{ 21 | InputObject = $InputObject 22 | Compress = $Compress 23 | } 24 | ConvertTo-Json @ConvertParams | Out-File -FilePath $FilePath -Encoding $Encoding 25 | 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /functions/AD/Get-AdObjectDomain.ps1: -------------------------------------------------------------------------------- 1 | Function Get-AdObjectDomain { 2 | 3 | [cmdletbinding(DefaultParameterSetName = 'ADObject')] 4 | param 5 | ( 6 | [parameter(Mandatory, ParameterSetName = 'ADObject')] 7 | [ValidateScript( {Test-Member -InputObject $_ -Name CanonicalName})] 8 | $adobject 9 | , 10 | [parameter(Mandatory, ParameterSetName = 'ExchangeObject')] 11 | [ValidateScript( {Test-Member -InputObject $_ -Name Identity})] 12 | $ExchangeObject 13 | ) 14 | switch ($PSCmdlet.ParameterSetName) 15 | { 16 | 'ADObject' 17 | {[string]$domain = $adobject.canonicalname.split('/')[0]} 18 | 'ExchangeObject' 19 | {[string]$domain = $ExchangeObject.Identity.split('/')[0]} 20 | } 21 | $domain 22 | 23 | } 24 | -------------------------------------------------------------------------------- /functions/Utilities/Remove-ExistingObjectFromMultivaluedAttribute.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-ExistingObjectFromMultivaluedAttribute { 2 | 3 | [CmdletBinding()] 4 | param 5 | ( 6 | $ParentObject 7 | , 8 | $ChildObject 9 | , 10 | $MultiValuedAttributeName 11 | , 12 | $IdentityAttributeName 13 | ) 14 | $index = Get-ArrayIndexForValue -array $ParentObject.$MultiValuedAttributeName -value $ChildObject.$IdentityAttributeName -property $IdentityAttributeName 15 | $originalChildObjectContainer = @($ParentObject.$MultiValuedAttributeName) 16 | $newChildObjectContainer = @($originalChildObjectContainer | Where-Object -FilterScript {$_.Identity -ne $originalChildObjectContainer[$index].$IdentityAttributeName}) 17 | $ParentObject.$MultiValuedAttributeName = $newChildObjectContainer 18 | $ParentObject 19 | 20 | } 21 | -------------------------------------------------------------------------------- /functions/Utilities/Get-ArrayIndexForValue.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ArrayIndexForValue 2 | { 3 | 4 | [cmdletbinding()] 5 | param( 6 | [parameter(mandatory = $true)] 7 | $array #The array for which you want to find a value's index 8 | , 9 | [parameter(mandatory = $true)] 10 | $value #The Value for which you want to find an index 11 | , 12 | [parameter()] 13 | $property #The property name for the value for which you want to find an index 14 | ) 15 | # this returns the first instance of the value in the array. If you have duplicate values additional handling is needed. 16 | if ([string]::IsNullOrWhiteSpace($Property)) 17 | { 18 | Write-Verbose -Message 'Using Simple Match for Index' 19 | [array]::indexof($array, $value) 20 | }#if 21 | else 22 | { 23 | Write-Verbose -Message 'Using Property Match for Index' 24 | [array]::indexof($array.$property, $value) 25 | }#else 26 | 27 | } 28 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-ADPsDrive.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ADPsDrive { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [string]$Name 7 | , 8 | [switch]$IsRootofDirectory 9 | ) 10 | 11 | #Check PSDrive: Should be AD, Should be Root of the PSDrive 12 | Try 13 | { 14 | $ADPSDrive = Get-PSDrive -name $name -PSProvider ActiveDirectory -ErrorAction Stop 15 | } 16 | Catch 17 | { 18 | Write-Verbose -message "No PSDrive with Name $name and PSProviderType ActiveDirectory exists." 19 | $false 20 | } 21 | 22 | $PSDriveTests = @{ 23 | ProviderIsActiveDirectory = $($ADPSDrive.Provider.name -eq 'ActiveDirectory') 24 | } 25 | 26 | if ($IsRootDSE) 27 | { 28 | $psdriveTests.RootIsRootOfDirectory = ($ADPSDrive.Root -eq '//RootDSE/') 29 | } 30 | 31 | if ($PSDriveTests.Values -contains $false) 32 | { 33 | $false 34 | } 35 | else 36 | { 37 | $true 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /functions/Exchange/Add-ExchangeProxyAddressToTestExchangeProxyAddress.ps1: -------------------------------------------------------------------------------- 1 | Function Add-ExchangeProxyAddressToTestExchangeProxyAddress { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [string]$ProxyAddress 7 | , 8 | [string]$ObjectGUID #should be the AD ObjectGuid 9 | , 10 | [parameter()] 11 | [ValidateSet('SMTP', 'X500')] 12 | [string]$ProxyAddressType = 'SMTP' 13 | ) 14 | 15 | #Fix the ProxyAddress if needed 16 | if ($ProxyAddress -notlike "{$proxyaddresstype}:*") 17 | { 18 | $ProxyAddress = "${$proxyaddresstype}:$ProxyAddress" 19 | } 20 | #Test the Proxy Address 21 | if ($Script:TestExchangeProxyAddress.ContainsKey($ProxyAddress)) 22 | { 23 | Write-Log -Message "ProxyAddress $ProxyAddress already exists in the TestExchangeProxyAddress Table" -EntryType Failed 24 | $false 25 | } 26 | else 27 | { 28 | $Script:TestExchangeProxyAddress.$ProxyAddress = @() 29 | $Script:TestExchangeProxyAddress.$ProxyAddress += $ObjectGUID 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mike Campbell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /functions/Utilities/Get-EmptyDirectory.ps1: -------------------------------------------------------------------------------- 1 | Function Get-EmptyDirectory 2 | { 3 | [cmdletbinding()] 4 | param( 5 | # Path to the parent directory to check for empty directory(ies) 6 | [Parameter(Mandatory)] 7 | [ValidateScript({Test-Path -PathType Container -Path $_})] 8 | [string[]]$Path 9 | , 10 | # Use Recursion 11 | [Parameter()] 12 | [switch] 13 | $Recurse 14 | ) 15 | 16 | begin 17 | { 18 | $Empty = [system.collections.generic.List[PSObject]]::new() 19 | } 20 | process 21 | { 22 | foreach ($p in $Path) 23 | { 24 | $gciParams = @{ 25 | Path = $p 26 | Recurse = $Recurse 27 | Directory = $true 28 | } 29 | 30 | $Directories = Get-ChildItem @gciParams 31 | 32 | $Directories.where({ 33 | @($_.GetFileSystemInfos()).count -eq 0 34 | }).foreach({$Empty.Add($_)}) 35 | } 36 | } 37 | end 38 | { 39 | if ($Empty.count -ge 1) 40 | { 41 | $Empty 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /functions/TestFunctions/Test-IsWriteableDirectory.ps1: -------------------------------------------------------------------------------- 1 | Function Test-IsWriteableDirectory { 2 | 3 | #Credits to the following: 4 | #http://poshcode.org/2236 5 | #http://stackoverflow.com/questions/9735449/how-to-verify-whether-the-share-has-write-access 6 | [CmdletBinding()] 7 | param 8 | ( 9 | [parameter()] 10 | [ValidateScript( 11 | { 12 | $IsContainer = Test-Path -Path ($_) -PathType Container 13 | if ($IsContainer) 14 | { 15 | $Item = Get-Item -Path $_ 16 | if ($item.PsProvider.Name -eq 'FileSystem') {$true} 17 | else {$false} 18 | } 19 | else {$false} 20 | } 21 | )] 22 | [string]$Path 23 | ) 24 | try 25 | { 26 | $testPath = Join-Path -Path $Path -ChildPath ([IO.Path]::GetRandomFileName()) 27 | New-Item -Path $testPath -ItemType File -ErrorAction Stop > $null 28 | $true 29 | } 30 | catch 31 | { 32 | $false 33 | } 34 | finally 35 | { 36 | Remove-Item -Path $testPath -ErrorAction SilentlyContinue 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /functions/Utilities/Compare-ColumnMapping.ps1: -------------------------------------------------------------------------------- 1 | function Compare-ColumnMapping 2 | { 3 | [cmdletbinding(DefaultParameterSetName = 'HashMap')] 4 | param( 5 | [parameter(Mandatory)] 6 | [psobject[]]$DataSourceSample 7 | , 8 | [parameter(ParameterSetName = 'HashMap',Mandatory)] 9 | [hashtable]$ColumnMap 10 | , 11 | [parameter(ParameterSetName = 'HashMap',Mandatory)] 12 | [ValidateSet('Keys','Values')] 13 | [string]$KeysOrValues 14 | , 15 | [parameter(ParameterSetName = 'Table',Mandatory)] 16 | [Microsoft.SqlServer.Management.Smo.Table]$Table 17 | ) 18 | 19 | $DSSCompare = @(($DataSourceSample[0] | get-Member -MemberType properties | Sort-Object -Property Name).Name) 20 | 21 | switch ($PSCmdlet.ParameterSetName) 22 | { 23 | 'HashMap' 24 | { 25 | $CMCompare = @($ColumnMap.$($KeysOrValues) | Sort-Object) 26 | } 27 | 'Table' 28 | { 29 | $CMCompare = @($Table.Columns.Name | Sort-Object) 30 | } 31 | } 32 | 33 | Compare-Object -ReferenceObject $DSSCompare -DifferenceObject $CMCompare -IncludeEqual -CaseSensitive | Sort-Object -Property InputObject 34 | 35 | } -------------------------------------------------------------------------------- /functions/Utilities/Get-AllParametersWithAValue.ps1: -------------------------------------------------------------------------------- 1 | Function Get-AllParametersWithAValue { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | $BoundParameters #$PSBoundParameters 7 | , 8 | $AllParameters #$MyInvocation.MyCommand.Parameters 9 | , 10 | [switch]$IncludeCommon 11 | , 12 | $Scope = 1 13 | ) 14 | $getAllParameterParams = @{ 15 | BoundParameters = $BoundParameters 16 | AllParameters = $AllParameters 17 | } 18 | if ($IncludeCommon -eq $true) {$getAllParametersParams.IncludeCommon = $true} 19 | $AllParameterKeys = Get-AllParameter @getAllParameterParams 20 | $AllParametersWithAValue = @( 21 | foreach ($k in $AllParameterKeys) 22 | { 23 | try 24 | { 25 | Get-Variable -Name $k -Scope $Scope -ErrorAction Stop | Where-Object -FilterScript {$null -ne $_.Value -and -not [string]::IsNullOrWhiteSpace($_.Value)} 26 | } 27 | catch 28 | { 29 | #don't care if a particular variable is not found 30 | Write-Verbose -Message "$k was not found" 31 | } 32 | } 33 | ) 34 | $AllParametersWithAValue 35 | 36 | } 37 | -------------------------------------------------------------------------------- /functions/Utilities/Remove-Member.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-Member { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline)] 7 | [Alias('Object')] 8 | [PSCustomObject[]]$InputObject 9 | , 10 | [parameter(Mandatory)] 11 | [string]$Member 12 | ) 13 | begin { 14 | $ErrorActionPreference = 'Stop' 15 | } 16 | process 17 | { 18 | foreach ($o in $InputObject) 19 | { 20 | switch ($o.GetType().name) 21 | { 22 | 'PSCustomObject' 23 | { 24 | Write-Verbose -Message "Using psobject.members.Remove($Member)" 25 | $o.psobject.Members.Remove($Member) 26 | } 27 | Default 28 | { 29 | Write-Verbose -Message "Using Select-Object -property * -ExcludeProperty $Member" 30 | Write-Error -Message "Remove-Member is not effective for objects not of type PSCustomObject. This object is of type $($o.gettype().name)" 31 | #$o = $o | Select-Object -Property * -ExcludeProperty $Member 32 | } 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /scripts/LEDTickers/StatusFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Set-LEDTickerOffline 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [parameter()] 6 | [ValidateLength(0,11)] 7 | [string]$MeetingText 8 | ) 9 | $te1 = New-LEDTextElement -text 'OFFLINE' -size 3 -align C -color CC9900 -x 31 -y 10 10 | $le1 = New-LEDLineElement -color CC3300 -x1 0 -x2 63 -y1 12 -y2 12 11 | $te2 = New-LEDTextElement -text $MeetingText.ToUpper() -size 2 -align C -color CC9900 -x 31 -y 20 12 | Out-LEDTicker -Element $te1, $le1, $te2 13 | 14 | } 15 | 16 | function Set-LEDTickerOnAir 17 | { 18 | [CmdletBinding()] 19 | param( 20 | [switch]$mic 21 | , 22 | [switch]$Video 23 | ) 24 | 25 | $MicText = switch ($mic) { $true { 'MIC ON' } $false { 'MIC OFF' } } 26 | $VidText = switch ($Video) { $true { 'VIDEO ON' } $false { 'VIDEO OFF' } } 27 | 28 | $te1 = New-LEDTextElement -text 'ON-AIR' -size 3 -align C -color FFCC00 -x 31 -y 10 29 | $le1 = New-LEDLineElement -color CC3300 -x1 0 -x2 63 -y1 12 -y2 12 30 | $te2 = New-LEDTextElement -text $MicText -size 2 -align C -color FFCC00 -x 31 -y 20 31 | $te3 = New-LEDTextElement -text $VidText -size 2 -align C -color FFCC00 -x 31 -y 28 32 | Out-LEDTicker -Element $te1, $le1, $te2, $te3 33 | 34 | } -------------------------------------------------------------------------------- /dev/Get-ExchangeRecipient.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ExchangeRecipient { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 7 | [string[]]$Identity 8 | , 9 | [parameter(Mandatory)] 10 | [System.Management.Automation.Runspaces.PSSession[]]$ExchangeSession 11 | ) 12 | DynamicParam 13 | { 14 | $dictionary = New-ExchangeOrganizationDynamicParameter -Mandatory -Multivalued 15 | $dictionary 16 | } 17 | begin 18 | { 19 | #Test the ExchangeSession(s) 20 | } 21 | process 22 | { 23 | foreach ($id in $Identity) 24 | { 25 | $InvokeCommandParams = @{ 26 | #ErrorAction = 'Stop' 27 | WarningAction = 'SilentlyContinue' 28 | ErrorAction = 'Continue' 29 | scriptblock = [scriptblock] {Get-Recipient -Identity $id -WarningAction SilentlyContinue -ErrorAction Continue} 30 | Cmdlet = 'Get-Recipient' 31 | } 32 | foreach ($s in $ExchangeSession) 33 | { 34 | $InvokeCommandParams.Session = $s 35 | Invoke-Command @InvokeCommandParams 36 | } 37 | } 38 | }#process 39 | 40 | } 41 | -------------------------------------------------------------------------------- /dev/NewFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Get-DistributionGroupMemberExpanded 2 | { 3 | [CmdletBinding()] 4 | param 5 | ( 6 | $Identity 7 | ) 8 | $BaseGroupMembership = @(Get-DistributionGroupMember -Identity $Identity -ResultSize Unlimited) 9 | $AllResolvedMembers = @( 10 | do 11 | { 12 | $BaseGroupMembership | Where-Object -FilterScript {$_.RecipientTypeDetails -notlike '*group*'} 13 | $RemainingGroupMembers = @($BaseGroupMembership | Where-Object -FilterScript {$_.RecipientTypeDetails -like '*group*'}) 14 | $BaseGroupMembership = @($RemainingGroupMembers | ForEach-Object {Get-DistributionGroupMember -Identity $_.guid.guid}) 15 | 16 | } 17 | until ($BaseGroupMembership.count -eq 0) 18 | ) 19 | Write-Output -InputObject $AllResolvedMembers 20 | } 21 | 22 | function Get-ADObjectExpandedMembership 23 | { 24 | param 25 | ( 26 | [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] 27 | [string] 28 | $Identity 29 | ) 30 | 31 | process 32 | { 33 | $ADObject = Get-ADObject -Identity $Identity 34 | $DN = $ADObject.DistinguishedName 35 | $strFilter = "(member:1.2.840.113556.1.4.1941:=$DN)" 36 | Get-ADGroup -LDAPFilter $strFilter -ResultSetSize Unlimited 37 | } 38 | } -------------------------------------------------------------------------------- /functions/Exchange/Test-ExchangeCommandExists.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ExchangeCommandExists 2 | { 3 | 4 | [cmdletbinding(DefaultParameterSetName = 'Organization')] 5 | param( 6 | [parameter(Mandatory, Position = 1)] 7 | [ValidateScript( { $_ -like '*-*' })] 8 | [string]$cmdlet 9 | , 10 | [switch]$checkConnection 11 | , 12 | [string]$ExchangeOrganization 13 | )#Param 14 | begin 15 | { 16 | # Bind the dynamic parameter to a friendly variable 17 | $orgobj = $Script:CurrentOrgAdminProfileSystems | Where-Object SystemType -eq 'ExchangeOrganizations' | Where-Object { $_.name -eq $ExchangeOrganization } 18 | $CommandPrefix = $orgobj.CommandPrefix 19 | if ($checkConnection -eq $true) 20 | { 21 | if ((Connect-Exchange -exchangeorganization $ExchangeOrganization) -ne $true) 22 | { throw ("Connection to Exchange Organization $ExchangeOrganization failed.") } 23 | } 24 | }#begin 25 | Process 26 | { 27 | #Build the Command String 28 | $commandstring = "$($cmdlet.split('-')[0])-$CommandPrefix$($cmdlet.split('-')[1])" 29 | 30 | #Store and Set and Restore ErrorAction Preference; Execute the command String 31 | Test-CommandExists -command $commandstring 32 | }#Process 33 | 34 | } 35 | -------------------------------------------------------------------------------- /functions/Utilities/Convert-StringBoolToBool.ps1: -------------------------------------------------------------------------------- 1 | function Convert-StringBoolToBool 2 | { 3 | [CmdletBinding(DefaultParameterSetName = 'AllPropertiesOfObject')] 4 | param( 5 | [parameter(ValueFromPipeline, ParameterSetName = 'AllPropertiesOfObject')] 6 | [object]$Object 7 | , 8 | [parameter(ParameterSetName = 'AllPropertiesOfObject')] 9 | [string[]]$IncludeProperty #use to include properties that might have an empty string. Sets them to $False. Otherwise, this only converts string members with a TRUE or FALSE string value. 10 | ) 11 | process 12 | { 13 | foreach ($o in $Object) 14 | { 15 | $stringMembers = @((Get-Member -InputObject $o -MemberType Properties).where( { $_.Definition -like 'string*' }).foreach( { $_.Name })) 16 | foreach ($sm in $stringMembers) 17 | { 18 | switch ($o.$sm) 19 | { 20 | 'TRUE' 21 | { $o.$sm = $true } 22 | 23 | 'FALSE' 24 | { $o.$sm = $false } 25 | 26 | {[string]::IsNullOrEmpty($_) -and $sm -in $IncludeProperty} 27 | { 28 | $o.$sm = $false 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /functions/UserInput/Read-FolderBrowserDialog.ps1: -------------------------------------------------------------------------------- 1 | Function Read-FolderBrowserDialog { 2 | 3 | # Show an Open Folder Dialog and return the directory selected by the user. 4 | [cmdletbinding()] 5 | Param( 6 | [string]$Description 7 | , 8 | [string]$InitialDirectory 9 | , 10 | [string]$RootDirectory 11 | , 12 | [switch]$NoNewFolderButton 13 | ) 14 | Add-Type -AssemblyName System.Windows.Forms 15 | $FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog 16 | if ($NoNewFolderButton) {$FolderBrowserDialog.ShowNewFolderButton = $false} 17 | if ($PSBoundParameters.ContainsKey('Description')) {$FolderBrowserDialog.Description = $Description} 18 | if ($PSBoundParameters.ContainsKey('InitialDirectory')) {$FolderBrowserDialog.SelectedPath = $InitialDirectory} 19 | if ($PSBoundParameters.ContainsKey('RootDirectory')) {$FolderBrowserDialog.RootFolder = $RootDirectory} 20 | $Result = $FolderBrowserDialog.ShowDialog() 21 | switch ($Result) 22 | { 23 | 'OK' 24 | { 25 | $folder = $FolderBrowserDialog.SelectedPath 26 | $folder 27 | } 28 | 'Cancel' 29 | { 30 | } 31 | } 32 | $FolderBrowserDialog.Dispose() 33 | Remove-Variable -Name FolderBrowserDialog 34 | 35 | } 36 | -------------------------------------------------------------------------------- /functions/Utilities/Get-SelfSignedCert.ps1: -------------------------------------------------------------------------------- 1 | Function Get-SelfSignedCert 2 | { 3 | [cmdletbinding()] 4 | param( 5 | $TenantName = "contoso.onmicrosoft.com" # Your tenant name (can something more descriptive as well) 6 | , 7 | $FilePath = "C:\Temp\PowerShellGraphCert.cer" # Where to export the certificate without the private key 8 | , 9 | $CertificateStorePath = "Cert:\CurrentUser\My" # What cert store you want it to be in 10 | , 11 | $ExpirationDate = (Get-Date).AddYears(2) # Expiration date of the new certificate 12 | ) 13 | # Splat for readability 14 | $CertificateSplat = @{ 15 | FriendlyName = "AzureADApp" 16 | DnsName = $TenantName 17 | CertStoreLocation = $CertificateStorePath 18 | NotAfter = $ExpirationDate 19 | KeyExportPolicy = "Exportable" 20 | KeySpec = "Signature" 21 | Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider" 22 | HashAlgorithm = "SHA256" 23 | } 24 | 25 | # Create certificate 26 | $Certificate = New-SelfSignedCertificate @CertificateSplat 27 | 28 | # Get certificate path 29 | $CertificatePath = Join-Path -Path $CertificateStorePath -ChildPath $Certificate.Thumbprint 30 | 31 | # Export certificate without private key 32 | Export-Certificate -Cert $CertificatePath -FilePath $FilePath | Out-Null 33 | } 34 | -------------------------------------------------------------------------------- /functions/DateTime/New-BackgroundTimer.ps1: -------------------------------------------------------------------------------- 1 | Function New-BackgroundTimer { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [string]$name 6 | , 7 | [parameter()] 8 | [validateset('Seconds', 'Minutes', 'Hours', 'Days')] 9 | $units = 'Minutes' 10 | , 11 | [parameter()] 12 | $length 13 | , 14 | [switch]$voice 15 | , 16 | [switch]$showprogress 17 | , 18 | [double]$Frequency = 1 19 | , 20 | [hashtable[]]$altReport #Units,Frequency,CountdownPoint 21 | ) 22 | $BackgroundTimerParams = @{ 23 | JobFunctions = @( 24 | 'New-Timer' 25 | 'Convert-HashTableToObject' 26 | ) 27 | Name = $name 28 | arguments = @($units, $length, $voice, $showprogress, $Frequency, $altReport) 29 | script = [string] { 30 | $newtimerparams = @{ 31 | Units = $args[0] 32 | Length = $args[1] 33 | Voice = $args[2] 34 | ShowProgress = $args[3] 35 | Frequency = $args[4] 36 | AltReport = $args[5] 37 | } 38 | New-Timer @newtimerparams 39 | } 40 | } 41 | 42 | Start-ComplexJob @BackgroundTimerParams 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /scripts/RipRow/VerbalPrompts.ps1: -------------------------------------------------------------------------------- 1 | $Movements = Import-JSON -FilePath $PSScriptRoot\Movements.json | Select-Object -ExpandProperty Movements 2 | $Workouts = Import-JSON -FilePath $PSScriptRoot\Workouts.json | Select-Object -ExpandProperty Workouts 3 | foreach ($w in $Workouts) 4 | { 5 | foreach ($s in $w.Steps) 6 | { 7 | $substeps = $movements | Where-Object {$_.name -eq $s.name} | Select-Object -ExpandProperty Steps 8 | Add-Member -InputObject $s -MemberType NoteProperty -Name SubSteps -Value $substeps 9 | } 10 | } 11 | 12 | function Start-Workout 13 | { 14 | [cmdletbinding()] 15 | param( 16 | $Identity 17 | , 18 | [int]$initialdelayseconds = 5 19 | ) 20 | $Workout = $Workouts | Where-Object Name -EQ $Identity 21 | if ($initialdelayseconds -ge 1) 22 | { 23 | Out-Speech -InputObject "Start $($Workout.name) in $initialdelayseconds seconds" -Volume 99 -SynchronousOutput 24 | New-Timer -Units Seconds -Length ($initialdelayseconds - 1) -ShowProgress -speech -Frequency 1 25 | } 26 | foreach ($s in $Workout.Steps) 27 | { 28 | Out-Speech -InputObject "Start $($s.Name) for $($s.Duration) $($s.DurationUnits)" -SynchronousOutput -Volume 99 -Rate 0 29 | New-Timer -Units $s.DurationUnits -Length $s.Duration -ShowProgress -Frequency 5 -speech 30 | } 31 | Out-Speech -InputObject "$($Workout.name) completed!" -Volume 99 32 | } -------------------------------------------------------------------------------- /functions/UserInput/Read-OpenFileDialog.ps1: -------------------------------------------------------------------------------- 1 | Function Read-OpenFileDialog { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [string]$WindowTitle 6 | , 7 | [string]$InitialDirectory 8 | , 9 | [string]$Filter = 'All files (*.*)|*.*' 10 | , 11 | [switch]$AllowMultiSelect 12 | ) 13 | Add-Type -AssemblyName System.Windows.Forms 14 | $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog 15 | $openFileDialog.Title = $WindowTitle 16 | if ($PSBoundParameters.ContainsKey('InitialDirectory')) { $openFileDialog.InitialDirectory = $InitialDirectory } 17 | $openFileDialog.Filter = $Filter 18 | if ($AllowMultiSelect) { $openFileDialog.MultiSelect = $true } 19 | $openFileDialog.ShowHelp = $true 20 | # Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE. 21 | $result = $openFileDialog.ShowDialog() 22 | switch ($Result) 23 | { 24 | 'OK' 25 | { 26 | if ($AllowMultiSelect) 27 | { 28 | $openFileDialog.Filenames 29 | } 30 | else 31 | { 32 | $openFileDialog.Filename 33 | } 34 | } 35 | 'Cancel' 36 | { 37 | } 38 | } 39 | $openFileDialog.Dispose() 40 | Remove-Variable -Name openFileDialog 41 | 42 | } 43 | -------------------------------------------------------------------------------- /functions/Utilities/Get-RemoteCertificate.ps1: -------------------------------------------------------------------------------- 1 | function Get-RemoteCertificate 2 | { 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [string] 7 | $ComputerName, 8 | 9 | [int] 10 | $Port = 443 11 | ) 12 | 13 | $Certificate = $null 14 | $TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient 15 | try 16 | { 17 | 18 | $TcpClient.Connect($ComputerName, $Port) 19 | $TcpStream = $TcpClient.GetStream() 20 | 21 | $Callback = { param($sender, $cert, $chain, $errors) return $true } 22 | 23 | $SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($TcpStream, $true, $Callback) 24 | try 25 | { 26 | 27 | $SslStream.AuthenticateAsClient('') 28 | $Certificate = $SslStream.RemoteCertificate 29 | 30 | } 31 | finally 32 | { 33 | $SslStream.Dispose() 34 | } 35 | 36 | } 37 | finally 38 | { 39 | $TcpClient.Dispose() 40 | } 41 | 42 | if ($Certificate) 43 | { 44 | if ($Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) 45 | { 46 | $Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $Certificate 47 | } 48 | 49 | Write-Output $Certificate 50 | } 51 | } -------------------------------------------------------------------------------- /functions/AD/Get-ADAttributeRangeUpper.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ADAttributeRangeUpper { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory = $true, ParameterSetName = 'LDAPDisplayName')] 7 | [string]$LDAPDisplayName 8 | , 9 | [parameter(Mandatory = $true, ParameterSetName = 'CommonName')] 10 | [string]$CommonName 11 | ) 12 | $GetADAttributeSchemaParams = @{ 13 | ErrorAction = 'Stop' 14 | Properties = 'RangeUpper' 15 | } 16 | switch ($PSCmdlet.ParameterSetName) 17 | { 18 | 'LDAPDisplayName' 19 | { 20 | $GetADAttributeSchemaParams.lDAPDisplayName = $LDAPDisplayName 21 | } 22 | 'CommonName' 23 | { 24 | $GetADAttributeSchemaParams.CommonName = $CommonName 25 | } 26 | } 27 | try 28 | { 29 | $AttributeSchema = @(Get-ADAttributeSchema @GetADAttributeSchemaParams) 30 | if ($AttributeSchema.Count -eq 1) 31 | { 32 | if ($AttributeSchema[0].RangeUpper -eq $null) {Write-Output -InputObject 'Unlimited'} 33 | else {Write-Output -InputObject $AttributeSchema[0].RangeUpper} 34 | } 35 | else 36 | { 37 | Write-Warning -Message 'AD Attribute Not Found' 38 | } 39 | } 40 | catch 41 | { 42 | $myerror = $_ 43 | Write-Error $myerror 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /functions/ManagedInstalls/Get-LocalGitRepo.ps1: -------------------------------------------------------------------------------- 1 | Function Get-LocalGitRepo 2 | { 3 | 4 | [cmdletbinding()] 5 | param( 6 | $path = $(Get-Location).path 7 | , 8 | [switch]$Recurse 9 | ) 10 | Push-Location 11 | Set-Location -Path $path 12 | if ($true -eq $Recurse) 13 | { 14 | $ChildDirectories = Get-ChildItem -Directory 15 | foreach ($cd in $ChildDirectories) 16 | { 17 | Set-Location -LiteralPath $cd.FullName 18 | $GitStatus = Get-GitStatus 19 | if ($null -ne $GitStatus) 20 | { 21 | [pscustomobject]@{ 22 | LocalName = $cd.FullName 23 | Branch = $GitStatus.Branch 24 | Upstream = $GitStatus.Upstream 25 | BehindBy = $GitStatus.BehindBy 26 | AheadBy = $GitStatus.AheadBy 27 | } 28 | } 29 | } 30 | } 31 | else 32 | { 33 | $GitStatus = Get-GitStatus 34 | if ($null -ne $GitStatus) 35 | { 36 | [pscustomobject]@{ 37 | LocalName = $(Get-Location).path 38 | Branch = $GitStatus.Branch 39 | Upstream = $GitStatus.Upstream 40 | BehindBy = $GitStatus.BehindBy 41 | AheadBy = $GitStatus.AheadBy 42 | } 43 | } 44 | } 45 | Pop-Location 46 | 47 | } 48 | -------------------------------------------------------------------------------- /functions/DateTime/Get-TimeInZone.ps1: -------------------------------------------------------------------------------- 1 | function Get-TimeInZone { 2 | [CmdletBinding(DefaultParameterSetName = 'ZoneID')] 3 | param( 4 | [Parameter(Mandatory,ParameterSetName = 'ZoneID')] 5 | [string[]]$TimeZoneID 6 | , 7 | [Parameter(Mandatory,ParameterSetName = 'ZoneName')] 8 | [string[]]$TimeZoneName 9 | , 10 | [Parameter()] 11 | [DateTime]$DateTime = $(Get-Date) 12 | ) 13 | $Zones = @(switch ($PSCmdlet.ParameterSetName) 14 | { 15 | 'ZoneID' 16 | {$TimeZoneID} 17 | 'ZoneName' 18 | {$TimeZoneName} 19 | }) 20 | 21 | foreach ($tz in $Zones) 22 | { 23 | $zone = Get-TimeZone -Id $tz 24 | $ZoneDateTime = Get-Date $([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($DateTime,$tz)) 25 | switch ($zone.IsDaylightSavingTime($ZoneDateTime)) 26 | { 27 | $true 28 | { 29 | [PSCustomObject]@{ 30 | DateTime = Get-Date -Date $([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($DateTime,$tz)) 31 | ZoneName = $zone.DaylightName 32 | } 33 | } 34 | $false 35 | { 36 | [PSCustomObject]@{ 37 | DateTime = Get-Date -Date $([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($DateTime,$tz)) 38 | ZoneName = $zone.StandardName 39 | } 40 | } 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /functions/Exchange/Remove-EmailAddressFromMailbox.ps1: -------------------------------------------------------------------------------- 1 | function Remove-EmailAddressFromMailbox 2 | { 3 | <# 4 | .SYNOPSIS 5 | Removes email address(es) from the emailaddresses attribute of a mailbox 6 | .DESCRIPTION 7 | Removes email address(es) from the emailaddresses attribute of a mailbox. Preserves all the other existing addresses. 8 | .EXAMPLE 9 | Add-EmailAddressToMailbox -Identity dave@contoso.com -emailaddress @('dave@wingtiptoys.com','dave@woodgrovebank.com') 10 | Adds the two addresses provided to mailbox dave@contoso.com 11 | #> 12 | 13 | [cmdletbinding(SupportsShouldProcess)] 14 | param( 15 | # An identifier for the target mailbox which Set-Mailbox -Identity parameter will accept. 16 | [parameter(Mandatory)] 17 | [string]$Identity 18 | , 19 | # Email Addres(es) to remove from the emailaddresses attribute in Exchange 20 | [parameter(Mandatory)] 21 | [string[]]$EmailAddress 22 | ) 23 | 24 | $EmailAddresses = @{ 25 | Remove = @() 26 | } 27 | 28 | foreach ($e in $EmailAddress) 29 | { 30 | $EmailAddresses.Add += $e 31 | } 32 | 33 | $SetMailboxParams = @{ 34 | Identity = $Identity 35 | EmailAddresses = $EmailAddresses 36 | } 37 | 38 | if ($PSCmdlet.ShouldProcess($Identity,"Set-Mailbox -EmailAddresses $($SetMailboxParams.EmailAddresses | ConvertTo-JSON -Compress)")) 39 | { 40 | Set-Mailbox @SetMailboxParams 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /functions/Exchange/Add-EmailAddressToMailbox.ps1: -------------------------------------------------------------------------------- 1 | function Add-EmailAddressToMailbox 2 | { 3 | <# 4 | .SYNOPSIS 5 | Adds email address(es) to the emailaddresses attribute of a mailbox 6 | .DESCRIPTION 7 | Adds email address(es) to the emailaddresses attribute of a mailbox. Preserves all the existing addresses. Will fail with error if a duplicate address is attempted. 8 | .EXAMPLE 9 | Add-EmailAddressToMailbox -Identity dave@contoso.com -emailaddress @('dave@wingtiptoys.com','dave@woodgrovebank.com') 10 | Adds the two addresses provided to mailbox dave@contoso.com 11 | #> 12 | 13 | [cmdletbinding(SupportsShouldProcess)] 14 | param( 15 | # An identifier for the target mailbox which Set-Mailbox -Identity parameter will accept. 16 | [parameter(Mandatory)] 17 | [string]$Identity 18 | , 19 | # Email Addres(es) to add to the emailaddresses attribute in Exchange 20 | [parameter(Mandatory)] 21 | [string[]]$EmailAddress 22 | ) 23 | 24 | $EmailAddresses = @{ 25 | Add = @() 26 | } 27 | 28 | foreach ($e in $EmailAddress) 29 | { 30 | $EmailAddresses.Add += $e 31 | } 32 | 33 | $SetMailboxParams = @{ 34 | Identity = $Identity 35 | EmailAddresses = $EmailAddresses 36 | } 37 | 38 | if ($PSCmdlet.ShouldProcess($Identity,"Set-Mailbox -EmailAddresses $($SetMailboxParams.EmailAddresses | ConvertTo-JSON -Compress)")) 39 | { 40 | Set-Mailbox @SetMailboxParams 41 | } 42 | } -------------------------------------------------------------------------------- /functions/Exchange/New-TestExchangeAlias.ps1: -------------------------------------------------------------------------------- 1 | Function New-TestExchangeAlias { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory = $true)] 7 | [System.Management.Automation.Runspaces.PSSession]$ExchangeSession 8 | ) 9 | $Script:TestExchangeAlias = @{} 10 | $AllRecipients = Invoke-Command -Session $ExchangeSession -scriptblock {Get-Recipient -ResultSize Unlimited -ErrorAction Stop} 11 | $RecordCount = $AllRecipients.count 12 | $cr = 0 13 | foreach ($r in $AllRecipients) 14 | { 15 | $cr++ 16 | $writeProgressParams = @{ 17 | Activity = 'Processing Recipient Alias for Test-ExchangeAlias. Building Global Variable which future uses of Test-ExchangeAlias will use unless the -RefreshAliasData parameter is used.' 18 | Status = "Record $cr of $RecordCount" 19 | PercentComplete = $cr / $RecordCount * 100 20 | CurrentOperation = "Processing Recipient: $($r.GUID.tostring())" 21 | } 22 | Write-Progress @writeProgressParams 23 | $alias = $r.alias 24 | if ($Script:TestExchangeAlias.ContainsKey($alias)) 25 | { 26 | $Script:TestExchangeAlias.$alias += $r.guid.tostring() 27 | } 28 | else 29 | { 30 | $Script:TestExchangeAlias.$alias = @() 31 | $Script:TestExchangeAlias.$alias += $r.guid.tostring() 32 | } 33 | } 34 | Write-Progress @writeProgressParams -Completed 35 | 36 | } 37 | -------------------------------------------------------------------------------- /functions/Utilities/Import-JSON.ps1: -------------------------------------------------------------------------------- 1 | Function Import-JSON 2 | { 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 7 | [Alias('PSPath', 'FullName')] 8 | [ValidateScript( {Test-Path -Path $_})] 9 | [string[]]$FilePath 10 | , 11 | [parameter()] 12 | [validateSet('Unicode', 'UTF7', 'UTF8', 'ASCII', 'UTF32', 'BigEndianUnicode', 'Default', 'OEM')] 13 | $Encoding 14 | ) 15 | begin 16 | { 17 | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState 18 | } 19 | process 20 | { 21 | foreach ($f in $FilePath) 22 | { 23 | Write-Verbose -Message "Processing path $f" 24 | $GetContentParams = @{ 25 | Path = $f 26 | Raw = $true 27 | } 28 | if ($null -ne $Encoding) 29 | {$GetContentParams.Encoding = $Encoding} 30 | try 31 | { 32 | $content = Get-Content @GetContentParams 33 | } 34 | catch 35 | { 36 | $_ 37 | } 38 | if ($null -eq $content -or $content.Length -lt 1) 39 | { 40 | throw("No content found in file $f") 41 | } 42 | else 43 | { 44 | ConvertFrom-Json -InputObject $content 45 | } 46 | } 47 | } 48 | end 49 | { 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /functions/Utilities/Test-IsWindowsTerminal.ps1: -------------------------------------------------------------------------------- 1 | 2 | #https://mikefrobbins.com/2024/05/16/detecting-windows-terminal-with-powershell/ 3 | function Test-IsWindowsTerminal { 4 | [CmdletBinding()] 5 | param () 6 | 7 | # Check if PowerShell version is 5.1 or below, or if running on Windows 8 | if ($PSVersionTable.PSVersion.Major -le 5 -or $IsWindows -eq $true) { 9 | $currentPid = $PID 10 | 11 | # Loop through parent processes to check if Windows Terminal is in the hierarchy 12 | while ($currentPid) { 13 | try { 14 | $process = Get-CimInstance Win32_Process -Filter "ProcessId = $currentPid" -ErrorAction Stop -Verbose:$false 15 | } catch { 16 | # Return false if unable to get process information 17 | return $false 18 | } 19 | 20 | Write-Verbose -Message "ProcessName: $($process.Name), Id: $($process.ProcessId), ParentId: $($process.ParentProcessId)" 21 | 22 | # Check if the current process is Windows Terminal 23 | if ($process.Name -eq 'WindowsTerminal.exe') { 24 | return $true 25 | } else { 26 | # Move to the parent process 27 | $currentPid = $process.ParentProcessId 28 | } 29 | } 30 | 31 | # Return false if Windows Terminal is not found in the hierarchy 32 | return $false 33 | } else { 34 | Write-Verbose -Message 'Exiting due to non-Windows environment' 35 | return $false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /functions/Utilities/Convert-HashtableToObject.ps1: -------------------------------------------------------------------------------- 1 | Function Convert-HashtableToObject { 2 | 3 | [CmdletBinding()] 4 | PARAM 5 | ( 6 | [Parameter(ValueFromPipeline, Mandatory)] 7 | [HashTable]$hashtable 8 | , 9 | [switch]$Combine 10 | , 11 | [switch]$Recurse 12 | ) 13 | BEGIN 14 | { 15 | $output = @() 16 | } 17 | PROCESS 18 | { 19 | if ($recurse) 20 | { 21 | $keys = $hashtable.Keys | ForEach-Object { $_ } 22 | Write-Verbose -Message "Recursing $($Keys.Count) keys" 23 | foreach ($key in $keys) 24 | { 25 | if ($hashtable.$key -is [HashTable]) 26 | { 27 | $hashtable.$key = Convert-HashtableToObject -hashtable $hashtable.$key -Recurse # -Combine:$combine 28 | } 29 | } 30 | } 31 | if ($combine) 32 | { 33 | $output += @(New-Object -TypeName PSObject -Property $hashtable) 34 | Write-Verbose -Message "Combining Output = $($Output.Count) so far" 35 | } 36 | else 37 | { 38 | New-Object -TypeName PSObject -Property $hashtable 39 | } 40 | } 41 | END 42 | { 43 | if ($combine -and $output.Count -gt 1) 44 | { 45 | Write-Verbose -Message "Combining $($Output.Count) cached outputs" 46 | $output | Join-Object 47 | } 48 | else 49 | { 50 | $output 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /functions/Utilities/New-GenericList.ps1: -------------------------------------------------------------------------------- 1 | Function New-GenericList 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [parameter()] 6 | [string]$type 7 | ) 8 | 9 | switch ($type) 10 | { 11 | string 12 | { 13 | $list = [System.Collections.Generic.List[string]]::new() 14 | } 15 | integer 16 | { 17 | $list = [System.Collections.Generic.List[integer]]::new() 18 | } 19 | byte 20 | { 21 | $list = [System.Collections.Generic.List[byte]]::new() 22 | } 23 | float 24 | { 25 | $list = [System.Collections.Generic.List[float]]::new() 26 | } 27 | double 28 | { 29 | $list = [System.Collections.Generic.List[double]]::new() 30 | } 31 | decimal 32 | { 33 | $list = [System.Collections.Generic.List[decimal]]::new() 34 | } 35 | hashtable 36 | { 37 | $list = [System.Collections.Generic.List[hashtable]]::new() 38 | } 39 | bool 40 | { 41 | $list = [System.Collections.Generic.List[bool]]::new() 42 | } 43 | default 44 | { 45 | $list = [System.Collections.Generic.List[psobject]]::new() 46 | } 47 | } 48 | 49 | # both of these output methods work to get the empty list to the caller 50 | # Write-Output is preferred by me since it clarifies that the generic list 51 | # is being sent and not a standard PowerShell array 52 | 53 | Write-output $list -NoEnumerate 54 | #,$list 55 | } -------------------------------------------------------------------------------- /functions/AD/Get-ADDomainNetBiosName.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ADDomainNetBiosName { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [parameter(ValueFromPipeline, Mandatory)] 6 | [string[]]$DNSRoot 7 | ) 8 | #If necessary, create the script:ADDomainDNSRootToNetBiosNameHash 9 | if (-not (Test-Path variable:script:ADDomainDNSRootToNetBiosNameHash)) 10 | { 11 | $script:ADDomainDNSRootToNetBiosNameHash = @{} 12 | } 13 | #Lookup the NetBIOSName for the domain in the script:ADDomainDNSRootToNetBiosNameHash 14 | if ($script:ADDomainDNSRootToNetBiosNameHash.containskey($DNSRoot)) 15 | { 16 | $NetBiosName = $script:ADDomainDNSRootToNetBiosNameHash.$DNSRoot 17 | } 18 | #or lookup the NetBIOSName from AD and add it to the script:ADDomainDNSRootToNetBiosNameHash 19 | else 20 | { 21 | try 22 | { 23 | $message = "Look up $DNSRoot NetBIOSName for the first time." 24 | Write-Log -Message $message -EntryType Attempting 25 | $NetBiosName = Get-ADDomain -Identity $DNSRoot -ErrorAction Stop | Select-Object -ExpandProperty NetBIOSName 26 | $script:ADDomainDNSRootToNetBiosNameHash.$DNSRoot = $NetBiosName 27 | Write-Log -Message $message -EntryType Succeeded 28 | } 29 | catch 30 | { 31 | $myerror = $_ 32 | Write-Log -Message $message -EntryType Failed -Verbose -ErrorLog 33 | Write-Log -Message $myerror.tostring() -ErrorLog 34 | $PSCmdlet.ThrowTerminatingError($myerror) 35 | } 36 | } 37 | #Return the NetBIOSName 38 | Write-Output $NetBiosName 39 | 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "PowerShell", 9 | "request": "launch", 10 | "name": "PowerShell Launch Current File", 11 | "script": "${file}", 12 | "args": [], 13 | "cwd": "${file}" 14 | }, 15 | { 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "name": "PowerShell Launch Current File in Temporary Console", 19 | "script": "${file}", 20 | "args": [], 21 | "cwd": "${file}", 22 | "createTemporaryIntegratedConsole": true 23 | }, 24 | { 25 | "type": "PowerShell", 26 | "request": "launch", 27 | "name": "PowerShell Launch Current File w/Args Prompt", 28 | "script": "${file}", 29 | "args": [ 30 | "${command:SpecifyScriptArgs}" 31 | ], 32 | "cwd": "${file}" 33 | }, 34 | { 35 | "type": "PowerShell", 36 | "request": "attach", 37 | "name": "PowerShell Attach to Host Process", 38 | "processId": "${command:PickPSHostProcess}", 39 | "runspaceId": 1 40 | }, 41 | { 42 | "type": "PowerShell", 43 | "request": "launch", 44 | "name": "PowerShell Interactive Session", 45 | "cwd": "" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /functions/Exchange/Test-ExchangeAlias.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ExchangeAlias { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [string]$Alias 6 | , 7 | [string[]]$ExemptObjectGUIDs 8 | , 9 | [switch]$ReturnConflicts 10 | , 11 | [parameter(Mandatory = $true)] 12 | [System.Management.Automation.Runspaces.PSSession]$ExchangeSession 13 | ) 14 | 15 | #Test the Alias 16 | $ReturnedObjects = @( 17 | try 18 | { 19 | invoke-command -Session $ExchangeSession -ScriptBlock {Get-Recipient -identity $using:Alias -ErrorAction Stop} -ErrorAction Stop 20 | Write-Verbose -Message "Existing object(s) Found for Alias $Alias" 21 | } 22 | catch 23 | { 24 | if ($_.categoryinfo -like '*ManagementObjectNotFoundException*') 25 | { 26 | Write-Verbose -Message "No existing object(s) Found for Alias $Alias" 27 | } 28 | else 29 | { 30 | throw($_) 31 | } 32 | } 33 | ) 34 | if ($ReturnedObjects.Count -ge 1) 35 | { 36 | $ConflictingGUIDs = @($ReturnedObjects | ForEach-Object {$_.guid.guid} | Where-Object {$_ -notin $ExemptObjectGUIDs}) 37 | if ($ConflictingGUIDs.count -gt 0) 38 | { 39 | if ($ReturnConflicts) 40 | { 41 | Return $ConflictingGUIDs 42 | } 43 | else 44 | { 45 | $false 46 | } 47 | } 48 | else 49 | { 50 | $true 51 | } 52 | } 53 | else 54 | { 55 | $true 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /functions/Exchange/Get-SortableSizeValue.ps1: -------------------------------------------------------------------------------- 1 | function Get-SortableSizeValue { 2 | <# 3 | .SYNOPSIS 4 | This function converts unsortable Exchange quota and limit value strings to a sortable value. 5 | .DESCRIPTION 6 | Exchange provides strings for quota and limit values that are not sortable, especially if the values span different scale tiers (MB, GB, etc.). An example of a string is "20 GB (21,474,836,480 bytes)". This function converts those values to a sortable value. It also preserves the "Unlimited" value that Exchange returns for attributes set with no limit. 7 | .EXAMPLE 8 | Get-SortableSizeValue -Value 20 GB (21,474,836,480 bytes) -Scale GB 9 | #> 10 | 11 | [cmdletbinding()] 12 | param( 13 | # 14 | [string]$Value 15 | , 16 | # 17 | [parameter()] 18 | [validateSet('KB', 'MB', 'GB', 'TB', 'PB')] 19 | [string]$Scale 20 | ) 21 | 22 | switch ($Value) { 23 | 'Unlimited' { 24 | 'Unlimited' 25 | } 26 | Default { 27 | $Bytes = $Value.tostring().split([char[]]'() ')[3].replace(',', '') 28 | [math]::Round($( 29 | switch ($Scale) { 30 | 'KB' 31 | { $Bytes / 1KB } 32 | 'MB' 33 | { $Bytes / 1MB } 34 | 'GB' 35 | { $Bytes / 1GB } 36 | 'TB' 37 | { $Bytes / 1TB } 38 | 'PB' 39 | { $Bytes / 1PB } 40 | } 41 | ), 2 42 | ) 43 | 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /functions/Utilities/ConvertTo-MarkDown.ps1: -------------------------------------------------------------------------------- 1 | Function ConvertTo-Markdown { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | Param ( 5 | [Parameter( 6 | Mandatory = $true, 7 | Position = 0, 8 | ValueFromPipeline = $true 9 | )] 10 | [PSObject[]]$collection 11 | ) 12 | 13 | Begin { 14 | $items = @() 15 | $columns = @{} 16 | } 17 | 18 | Process { 19 | ForEach($item in $collection) { 20 | $items += $item 21 | 22 | $item.PSObject.Properties | ForEach-Object { 23 | if ($null -ne $_.Value ){ 24 | if(-not $columns.ContainsKey($_.Name) -or $columns[$_.Name] -lt $_.Value.ToString().Length) { 25 | $columns[$_.Name] = $_.Value.ToString().Length 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | End { 33 | ForEach($key in $($columns.Keys)) { 34 | $columns[$key] = [Math]::Max($columns[$key], $key.Length) 35 | } 36 | 37 | $header = @() 38 | ForEach($key in $columns.Keys) { 39 | $header += ('{0,-' + $columns[$key] + '}') -f $key 40 | } 41 | $header -join ' | ' 42 | 43 | $separator = @() 44 | ForEach($key in $columns.Keys) { 45 | $separator += '-' * $columns[$key] 46 | } 47 | $separator -join ' | ' 48 | 49 | ForEach($item in $items) { 50 | $values = @() 51 | ForEach($key in $columns.Keys) { 52 | $values += ('{0,-' + $columns[$key] + '}') -f $item.($key) 53 | } 54 | $values -join ' | ' 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /functions/Utilities/Convert-SecureStringToString.ps1: -------------------------------------------------------------------------------- 1 | Function Convert-SecureStringToString { 2 | 3 | <# 4 | .SYNOPSIS 5 | Decrypts System.Security.SecureString object that were created by the user running the function. Does NOT decrypt SecureString Objects created by another user. 6 | .DESCRIPTION 7 | Decrypts System.Security.SecureString object that were created by the user running the function. Does NOT decrypt SecureString Objects created by another user. 8 | .PARAMETER SecureString 9 | Required parameter accepts a System.Security.SecureString object from the pipeline or by direct usage of the parameter. Accepts multiple inputs. 10 | .EXAMPLE 11 | Decrypt-SecureString -SecureString $SecureString 12 | .EXAMPLE 13 | $SecureString1,$SecureString2 | Decrypt-SecureString 14 | .LINK 15 | This function is based on the code found at the following location: 16 | http://blogs.msdn.com/b/timid/archive/2009/09/09/powershell-one-liner-decrypt-securestring.aspx 17 | .INPUTS 18 | System.Security.SecureString 19 | .OUTPUTS 20 | System.String 21 | #> 22 | 23 | [cmdletbinding()] 24 | param ( 25 | [parameter(ValueFromPipeline = $True)] 26 | [securestring[]]$SecureString 27 | ) 28 | 29 | BEGIN {} 30 | PROCESS 31 | { 32 | foreach ($ss in $SecureString) 33 | { 34 | if ($ss -is 'SecureString') 35 | {[Runtime.InteropServices.marshal]::PtrToStringAuto([Runtime.InteropServices.marshal]::SecureStringToBSTR($ss))} 36 | } 37 | } 38 | END {} 39 | 40 | } 41 | -------------------------------------------------------------------------------- /functions/Utilities/Get-AvailableExceptionsList.ps1: -------------------------------------------------------------------------------- 1 | Function Get-AvailableExceptionsList { 2 | 3 | <# 4 | .Synopsis Retrieves all available Exceptions to construct ErrorRecord objects. 5 | .Description Retrieves all available Exceptions in the current session to construct ErrorRecord objects. 6 | .Example $availableExceptions = Get-AvailableExceptionsList Description =========== Stores all available Exception objects in the variable 'availableExceptions'. 7 | .Example Get-AvailableExceptionsList | Set-Content $env:TEMP\AvailableExceptionsList.txt Description =========== Writes all available Exception objects to the 'AvailableExceptionsList.txt' file in the user's Temp directory. 8 | .Inputs None 9 | .Outputs System.String 10 | .Link New-ErrorRecord 11 | .Notes Name: Get-AvailableExceptionsList Original Author: Robert Robelo ModifiedBy: Mike Campbell 12 | #> 13 | [CmdletBinding()] 14 | param() 15 | $irregulars = 'Dispose|OperationAborted|Unhandled|ThreadAbort|ThreadStart|TypeInitialization' 16 | $appDomains = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object {-not $_.IsDynamic} 17 | $ExportedTypes = $appDomains | ForEach-Object {$_.GetExportedTypes()} 18 | $Exceptions = $ExportedTypes | Where-Object {$_.name -like '*exception*' -and $_.name -notmatch $irregulars} 19 | $exceptionsWithGetConstructorsMethod = $Exceptions | Where-Object -FilterScript {'GetConstructors' -in @($_ | Get-Member -MemberType Methods | Select-Object -ExpandProperty Name)} 20 | $exceptionsWithGetConstructorsMethod | Select-Object -ExpandProperty FullName 21 | 22 | } 23 | -------------------------------------------------------------------------------- /functions/Utilities/Group-Join.ps1: -------------------------------------------------------------------------------- 1 | Function Group-Join 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [Parameter(ValueFromPipeline)] 6 | [psobject] 7 | ${InputObject}, 8 | 9 | [Parameter(ParameterSetName='DefaultParameter', Position=0)] 10 | [System.Object[]] 11 | #Property by which to group objects 12 | ${Property}, 13 | 14 | [Parameter(ParameterSetName='DefaultParameter', Mandatory)] 15 | [string[]] 16 | ${JoinProperty}, 17 | 18 | [Parameter(ParameterSetName='DefaultParameter', Mandatory)] 19 | [string] 20 | ${JoinDelimeter} 21 | ) 22 | begin 23 | { 24 | $collection = [System.Collections.Generic.List[psobject]]::new() 25 | } 26 | 27 | process 28 | { 29 | $collection.Add($InputObject) 30 | } 31 | 32 | end 33 | { 34 | $GroupedCollection = @($collection | Select-Object -Property * | Group-Object -Property $Property) 35 | $GroupedCollection.foreach({ 36 | $group = $_ 37 | switch ($group.count) 38 | { 39 | 1 40 | {$group.group} 41 | {$_ -ge 2} 42 | { 43 | $JPHash = [ordered]@{} 44 | $JoinProperty.foreach({ 45 | $JPHash.$_ = @($group.group.$_ | Sort-Object -Unique) -join $JoinDelimeter 46 | }) 47 | $JPHash.Keys.foreach({ 48 | $group.group[0].$_ = $JPHash.$_ 49 | }) 50 | $group.group[0] 51 | } 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Profile.psm1: -------------------------------------------------------------------------------- 1 | # Import Core Module Functions 2 | 3 | $FunctionsFolder = Join-Path -Path $PSScriptRoot -ChildPath 'functions' 4 | Write-Verbose -Message "Finding Functions from $FunctionsFolder" 5 | $FunctionFiles = Get-ChildItem -Recurse -File -Path $FunctionsFolder 6 | Write-Verbose -Message "Found $($FunctionFiles.count) Function Files To Load" 7 | foreach ($ff in $FunctionFiles) { if ($ff.fullname -like '*.ps1') { . $ff.fullname } } 8 | 9 | 10 | # Run Module Environment Setup 11 | $GetTimeZoneIDsScriptBlock = { 12 | param( 13 | $commandName, 14 | $parameterName, 15 | $wordToComplete, 16 | $commandAst, 17 | $fakeBoundParameters 18 | ) 19 | 20 | $CompletionNames = @([System.TimeZoneInfo]::GetSystemTimeZones()).where({ 21 | $_.ID -like "$wordToComplete*"}).ID 22 | 23 | foreach ($n in $CompletionNames) 24 | { 25 | [System.Management.Automation.CompletionResult]::new($n, $n, 'ParameterValue', $n) 26 | } 27 | } 28 | $GetTimeZoneNamesScriptBlock = { 29 | param( 30 | $commandName, 31 | $parameterName, 32 | $wordToComplete, 33 | $commandAst, 34 | $fakeBoundParameters 35 | ) 36 | 37 | $CompletionNames = @([System.TimeZoneInfo]::GetSystemTimeZones()).where({ 38 | $_.StandardName -like "$wordToComplete*"}).StandardName 39 | 40 | foreach ($n in $CompletionNames) 41 | { 42 | [System.Management.Automation.CompletionResult]::new($n, $n, 'ParameterValue', $n) 43 | } 44 | } 45 | 46 | Register-ArgumentCompleter -CommandName @( 47 | 'Get-TimeInZone' 48 | ) -ParameterName 'TimeZoneID' -ScriptBlock $GetTimeZoneIDsScriptBlock 49 | Register-ArgumentCompleter -CommandName @( 50 | 'Get-TimeInZone' 51 | ) -ParameterName 'TimeZoneName' -ScriptBlock $GetTimeZoneNamesScriptBlock -------------------------------------------------------------------------------- /functions/Utilities/Get-StringHash.ps1: -------------------------------------------------------------------------------- 1 | Function Get-StringHash 2 | { 3 | <# 4 | .SYNOPSIS 5 | Gets the hash value of a string 6 | 7 | .DESCRIPTION 8 | Gets the hash value of a string 9 | 10 | .PARAMETER String 11 | String to get the hash from 12 | 13 | .PARAMETER AlgorithmName 14 | Type of hash algorithm to use. Default is SHA1 15 | 16 | .EXAMPLE 17 | C:\PS> Get-StringHash "This is my string" 18 | Gets the SHA1 hash of the string 19 | 20 | .EXAMPLE 21 | C:\PS> Get-StringHash -AlgorithmName "MD5" -String "This is my string" 22 | Gets the MD5 hash of the string 23 | 24 | .EXAMPLE 25 | C:\PS> "This is my string" | Get-StringHash 26 | We can pass a string throught the pipeline 27 | 28 | .EXAMPLE 29 | Get-Content "c:\temp\hello_world.txt" | Get-StringHash 30 | 31 | .NOTE 32 | Adapted by ThatExactMike (https://www.thatexactmike.com) from http://dbadailystuff.com/2013/03/11/get-hash-a-powershell-hash-function/ with the intent of only providing string hashing functionality since Get-FileHash is now a native cmdlet in Microsoft.PowerShell.Utility 33 | #> 34 | param( 35 | [parameter(Position = 1, ValueFromPipeline)] 36 | [String]$String 37 | , 38 | [parameter()] 39 | [ValidateSet('MD5','SHA1','SHA256','SHA384','SHA512')] 40 | $AlgorithmName = "SHA1" 41 | ) 42 | Begin 43 | { 44 | $hashAlgorithm = [System.Security.Cryptography.HashAlgorithm]::Create($AlgorithmName) 45 | } 46 | Process 47 | { 48 | $md5StringBuilder = [System.Text.StringBuilder]::new() 49 | $ue = [System.Text.UTF8Encoding]::new() 50 | $hashAlgorithm.ComputeHash($ue.GetBytes($String)).foreach({[void] $md5StringBuilder.Append($_.ToString("x2"))}) 51 | $md5StringBuilder.ToString() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Pester.Tests.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") 2 | $Script:ModuleRoot = $(Split-Path -Path $PSScriptRoot -Parent) 3 | Write-Information -MessageData "Module Root is $script:ModuleRoot" -InformationAction Continue 4 | $Script:ModuleName = $(Split-Path -$Script:ModuleRoot -Leaf) 5 | Write-Information -MessageData "Module Name is $Script:ModuleName" -InformationAction Continue 6 | $Script:ModuleFile = $Script:ModuleFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psm1') 7 | Write-Information -MessageData "Module File is $($script:ModuleFile)" -InformationAction Continue 8 | $Script:ModuleSettingsFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psd1') 9 | Write-Information -MessageData "Module Settings File is $($script:ModuleSettingsFile)" -InformationAction Continue 10 | 11 | Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 12 | Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue 13 | Write-Information -MessageData "Import Module $Script:ModuleName" -InformationAction Continue 14 | Import-Module -Name $Script:ModuleSettingsFile -Force 15 | 16 | Describe "Public commands have Pester tests" -Tag 'Build' { 17 | $commands = Get-Command -Module $Script:ModuleName 18 | 19 | foreach ($command in $commands.Name) 20 | { 21 | $file = Get-ChildItem -Path "$Script:ModuleRoot\Tests" -Include "$command.Tests.ps1" -Recurse 22 | It "Should have a Pester test for [$command]" { 23 | $file.FullName | Should Not BeNullOrEmpty 24 | } 25 | } 26 | } 27 | 28 | Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 29 | Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue -------------------------------------------------------------------------------- /functions/Windows/Start-SelectedApp.ps1: -------------------------------------------------------------------------------- 1 | function Start-SelectedApp 2 | { 3 | [CmdletBinding()] 4 | param ( 5 | # Name of the App to Start 6 | [Parameter(Mandatory,ParameterSetName='Name')] 7 | [String[]] 8 | $Name 9 | , 10 | # Name of the Group of Apps to Start 11 | [Parameter(Mandatory,ParameterSetName='Group')] 12 | [ValidateSet('DailyCrypto')] 13 | [String] 14 | $Group 15 | ) 16 | 17 | begin 18 | { 19 | 20 | } 21 | 22 | process 23 | { 24 | $AppsToStart = @( 25 | switch ($pscmdlet.ParameterSetName) 26 | { 27 | 'Name' 28 | { 29 | $Name 30 | } 31 | 'Group' 32 | { 33 | switch ($Group) 34 | { 35 | 'DailyCrypto' 36 | { 37 | 'MetaMask' 38 | 'AAVE' 39 | 'Adamant' 40 | 'Balancer' 41 | 'Mai Finance' 42 | 'Impermax' 43 | 'Cometh' 44 | 'QuickSwap' 45 | 'ParaSwap' 46 | 'Mantra DAO' 47 | 'Polygon Beefy' 48 | } 49 | } 50 | } 51 | } 52 | ) 53 | foreach ($a in $AppsToStart) 54 | { 55 | $appID = $(Get-StartApps -Name $a).AppID.tostring() 56 | $path = 'shell:AppsFolder\' + $appID 57 | 58 | Start-Process $path 59 | 60 | } 61 | } 62 | 63 | end 64 | { 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /functions/Exchange/New-TestExchangeProxyAddress.ps1: -------------------------------------------------------------------------------- 1 | Function New-TestExchangeProxyAddress { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory = $true)] 7 | [System.Management.Automation.Runspaces.PSSession]$ExchangeSession 8 | ) 9 | $AllRecipients = Invoke-Command -Session $ExchangeSession -ScriptBlock {Get-Recipient -ResultSize Unlimited -ErrorAction Stop -WarningAction Continue} -ErrorAction Stop -WarningAction Continue 10 | $RecordCount = $AllRecipients.count 11 | $cr = 0 12 | $Script:TestExchangeProxyAddress = @{} 13 | foreach ($r in $AllRecipients) 14 | { 15 | $cr++ 16 | $writeProgressParams = @{ 17 | Activity = 'Processing Recipient Proxy Addresses for Test-ExchangeProxyAddress. Building Global Variable which future uses of Test-ExchangeProxyAddress will use unless the -RefreshProxyAddressData parameter is used.' 18 | Status = "Record $cr of $RecordCount" 19 | PercentComplete = $cr / $RecordCount * 100 20 | CurrentOperation = "Processing Recipient: $($r.GUID.tostring())" 21 | } 22 | Write-Progress @writeProgressParams 23 | $ProxyAddresses = $r.EmailAddresses 24 | foreach ($ProxyAddress in $ProxyAddresses) 25 | { 26 | if ($Script:TestExchangeProxyAddress.ContainsKey($ProxyAddress)) 27 | { 28 | $Script:TestExchangeProxyAddress.$ProxyAddress += $r.guid.tostring() 29 | } 30 | else 31 | { 32 | $Script:TestExchangeProxyAddress.$ProxyAddress = @() 33 | $Script:TestExchangeProxyAddress.$ProxyAddress += $r.guid.tostring() 34 | } 35 | } 36 | } 37 | Write-Progress @writeProgressParams -Completed 38 | 39 | } 40 | -------------------------------------------------------------------------------- /functions/ADSync/Export-ADSyncConnectorChanges.ps1: -------------------------------------------------------------------------------- 1 | Function Export-ADSyncConnectorChanges { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | $commandfolderpath = 'D:\Program Files\Microsoft Azure AD Sync\Bin' 7 | , 8 | [parameter()] 9 | $tempFileStorageFolder = 'D:\Temp' 10 | , 11 | [parameter(Mandatory)] 12 | #[validateSet(#make dynamic parameter)] 13 | [string]$connector 14 | , 15 | [parameter()] 16 | [validateSet('Disconnectors','ImportErrors','ExportErrors','PendingImports','PendingExports')] 17 | [string]$ChangeType 18 | , 19 | $OutputFileName 20 | ) 21 | 22 | $firstcommand = 'csexport.exe' 23 | $firstcommandfullpath = Join-Path $commandfolderpath $firstcommand 24 | $xmlfilePath = Join-Path -Path $tempFileStorageFolder -ChildPath ([IO.Path]::GetRandomFileName()) 25 | $filterString = $( 26 | switch ($ChangeType) 27 | { 28 | 'Disconnectors' 29 | {"/f:s"} 30 | 'ImportErrors' 31 | {"/f:i"} 32 | 'ExportErrors' 33 | {"/f:e"} 34 | 'PendingImports' 35 | {"/f:m"} 36 | 'PendingExports' 37 | {"/f:x"} 38 | } 39 | ) 40 | $SecondCommand = 'CSExportAnalyzer.exe' 41 | $SecondCommandFullPath = Join-Path -Path $commandfolderpath $SecondCommand 42 | $OutputFileFullPath = Join-Path $tempFileStorageFolder $OutputFileName 43 | $SecondCommandFullString = "'" 44 | #Run First Command 45 | & $firstcommandfullpath $connector $xmlfilepath $filterstring 46 | #Run Second Command 47 | & $SecondCommandFullPath $xmlfilePath > $OutputFileFullPath 48 | Remove-Item $xmlfilePath -Force 49 | Write-Verbose -Message "output file: $OutputFileFullPath" -Verbose 50 | 51 | } 52 | -------------------------------------------------------------------------------- /functions/Utilities/Get-UNCPath.ps1: -------------------------------------------------------------------------------- 1 | Function Get-UNCPath { 2 | 3 | [cmdletbinding()] 4 | param ( 5 | [parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] 6 | [validatescript({Test-Path -Path $_})] 7 | [string[]]$Path = $(Get-Location).Path 8 | ) 9 | begin 10 | { 11 | function Get-ContainerUNCPath 12 | { 13 | param( 14 | $ContainerPath 15 | ) 16 | Push-Location 17 | Set-Location -Path $ContainerPath 18 | $loc = Get-Location 19 | if ($null -eq $loc.Drive) {$loc.ProviderPath} 20 | else { 21 | switch ($null -eq $loc.Drive.DisplayRoot) 22 | { 23 | $true #not a network mapped drive - is local drive. 24 | { 25 | Join-Path -path (Join-Path -Path $('\\' + [System.Environment]::MachineName) -ChildPath $($loc.Drive.Name + '$')) -ChildPath $loc.Drive.CurrentLocation 26 | } 27 | $false #is a network mapped drive 28 | { 29 | Join-Path -Path $loc.Drive.DisplayRoot -ChildPath $loc.Drive.CurrentLocation 30 | } 31 | } 32 | } 33 | Pop-Location 34 | } 35 | } 36 | process 37 | { 38 | foreach ($p in $Path) 39 | { 40 | $item = Get-Item -Path $p 41 | switch ($item.PSIsContainer) 42 | { 43 | $true 44 | { 45 | Get-ContainerUNCPath -ContainerPath $item.FullName 46 | } 47 | $false 48 | { 49 | Join-Path -Path $(Get-ContainerUNCPath -ContainerPath $(Split-Path -Path $item.FullName -Parent)) -ChildPath $item.name 50 | } 51 | } 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /functions/AD/Get-QualifiedADUserObject.ps1: -------------------------------------------------------------------------------- 1 | Function Get-QualifiedADUserObject { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory)] 6 | [string]$ActiveDirectoryInstance 7 | , 8 | [string]$LDAPFilter 9 | #'(&(sAMAccountType=805306368)(proxyAddresses=SMTP:*)(extensionattribute15=DirSync))' 10 | #'(&((sAMAccountType=805306368))(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))' 11 | , 12 | [string[]]$Properties = $script:ADUserAttributes 13 | ) 14 | #Retrieve all qualified (per the filter)AD User Objects including the specified properties 15 | Write-StartFunctionStatus -CallingFunction $MyInvocation.MyCommand 16 | #Connect-ADInstance -ActiveDirectoryInstance $ActiveDirectoryInstance -ErrorAction Stop > $null 17 | Set-Location -Path "$($ActiveDirectoryInstance):\" 18 | $GetADUserParams = @{ 19 | ErrorAction = 'Stop' 20 | Properties = $Properties 21 | } 22 | if ($PSBoundParameters.ContainsKey('LDAPFilter')) 23 | { 24 | $GetADUserParams.LDAPFilter = $LDAPFilter 25 | } 26 | else 27 | { 28 | $GetADUserParams.Filter = '*' 29 | } 30 | Try 31 | { 32 | $message = 'Retrieve qualified Active Directory User Accounts.' 33 | Write-Log -verbose -message $message -EntryType Attempting 34 | $QualifiedADUsers = @(Get-ADUser @GetADUserParams | Select-Object -Property $Properties) 35 | $message = $message + " Count:$($QualifiedADUsers.count)" 36 | Write-Log -verbose -message $message -EntryType Succeeded 37 | Write-Output -InputObject $QualifiedADUsers 38 | } 39 | Catch 40 | { 41 | $myerror = $_ 42 | Write-Log -Message 'Active Directory user objects could not be retrieved.' -ErrorLog -Verbose 43 | Write-Log -Message $myerror.tostring() -ErrorLog 44 | } 45 | Write-EndFunctionStatus $MyInvocation.MyCommand 46 | 47 | } 48 | -------------------------------------------------------------------------------- /functions/Utilities/Measure-Property.ps1: -------------------------------------------------------------------------------- 1 | Function Measure-Property 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [Parameter(ParameterSetName = 'HashTable')] 6 | [Alias('AHT')] 7 | [switch] 8 | $AsHashTable, 9 | 10 | [Parameter(ValueFromPipeline = $true)] 11 | [psobject[]] 12 | $InputObject, 13 | 14 | [Parameter(Position = 0, Mandatory)] 15 | [string] 16 | $Property, 17 | 18 | [string] 19 | ${Culture}, 20 | 21 | [switch] 22 | ${CaseSensitive}) 23 | 24 | begin 25 | { 26 | try 27 | { 28 | $GroupObjectParams = @{ } + $PSBoundParameters 29 | #We never want an element with this version 30 | $GroupObjectParams['NoElement'] = $true 31 | #We never want group-object itself to create a hashtable 32 | $GroupObjectParams['AsHashTable'] = $false 33 | if ($true -eq $PSBoundParameters['AsHashTable']) 34 | { 35 | $hashtable = @{ } 36 | } 37 | $InputObjects = [System.Collections.ArrayList]::new() 38 | } 39 | catch 40 | { 41 | throw 42 | } 43 | } 44 | 45 | process 46 | { 47 | try 48 | { 49 | $null = $InputObjects.Add($_) 50 | } 51 | catch 52 | { 53 | throw 54 | } 55 | } 56 | 57 | end 58 | { 59 | try 60 | { 61 | $groups = $InputObjects | Group-Object @GroupObjectParams 62 | if ($true -eq $PSBoundParameters['AsHashTable']) 63 | { 64 | $groups | ForEach-Object { $hashtable["$($_.Name)"] = $_.Count } 65 | $hashtable 66 | } 67 | else 68 | { 69 | $groups | ForEach-Object { [pscustomobject]@{Name = $_.Name; Count = $_.Count; } } 70 | } 71 | } 72 | catch 73 | { 74 | throw 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/ScriptAnalyzer.Tests.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") 2 | $Script:ModuleRoot = $(Split-Path -Path $PSScriptRoot -Parent) 3 | Write-Information -MessageData "Module Root is $script:ModuleRoot" -InformationAction Continue 4 | $Script:ModuleName = $(Split-Path -$Script:ModuleRoot -Leaf) 5 | Write-Information -MessageData "Module Name is $Script:ModuleName" -InformationAction Continue 6 | $Script:ModuleFile = $Script:ModuleFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psm1') 7 | Write-Information -MessageData "Module File is $($script:ModuleFile)" -InformationAction Continue 8 | $Script:ModuleSettingsFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psd1') 9 | Write-Information -MessageData "Module Settings File is $($script:ModuleSettingsFile)" -InformationAction Continue 10 | 11 | Describe "All commands pass PSScriptAnalyzer rules" -Tag 'Build' { 12 | $rules = "$Script:ModuleRoot\ScriptAnalyzerSettings.psd1" 13 | $scripts = Get-ChildItem -Path $ModuleRoot -Include '*.ps1', '*.psm1', '*.psd1' -Recurse -Exclude 'ScriptAnalyzerSettings.psd1' | 14 | Where-Object -filterscript { $_.FullName -notmatch 'Classes' -and $_.FullName -notmatch 'Tests' } 15 | 16 | foreach ($script in $scripts) 17 | { 18 | Context $script.FullName { 19 | $results = Invoke-ScriptAnalyzer -Path $script.FullName -Settings $rules 20 | if ($results) 21 | { 22 | foreach ($rule in $results) 23 | { 24 | It $("Should {0} Severity:{1} Line {2}: {3}" -f $rule.RuleName, $rule.Severity, $rule.Line, $rule.Message) { 25 | $message = "violated" 26 | $message | Should Be "" 27 | } 28 | } 29 | } 30 | else 31 | { 32 | It "Should not fail any rules" { 33 | $results | Should BeNullOrEmpty 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-Member.ps1: -------------------------------------------------------------------------------- 1 | Function Test-Member { 2 | 3 | <# 4 | .ForwardHelpTargetName Get-Member 5 | .ForwardHelpCategory Cmdlet 6 | #> 7 | [CmdletBinding()] 8 | param( 9 | [Parameter(ValueFromPipeline)] 10 | [psobject] 11 | $InputObject, 12 | 13 | [Parameter(Position = 0)] 14 | [ValidateNotNullOrEmpty()] 15 | [String[]] 16 | $Name, 17 | 18 | [Alias('Type')] 19 | [Management.Automation.PSMemberTypes] 20 | $MemberType, 21 | 22 | [Management.Automation.PSMemberViewTypes] 23 | $View, 24 | 25 | [Switch] 26 | $Static, 27 | 28 | [Switch] 29 | $Force 30 | )#end param 31 | begin 32 | { 33 | try 34 | { 35 | $outBuffer = $null 36 | if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) 37 | { 38 | $PSBoundParameters['OutBuffer'] = 1 39 | } 40 | $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-Member', [Management.Automation.CommandTypes]::Cmdlet) 41 | #PSScriptAnalyzer 1.16.1 mistakenly says "The variable 'members' is assigned but never used." about the following line. 42 | $scriptCmd = {& $wrappedCmd @PSBoundParameters | ForEach-Object -Begin {$members = @()} -Process {$members += $_} -End {$members.Count -ne 0}} 43 | $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) 44 | $steppablePipeline.Begin($PSCmdlet) 45 | } 46 | catch 47 | { 48 | throw 49 | } 50 | }#end begin 51 | process 52 | { 53 | try 54 | { 55 | $steppablePipeline.Process($_) 56 | } 57 | catch 58 | { 59 | throw 60 | } 61 | }#end process 62 | end 63 | { 64 | try 65 | { 66 | $steppablePipeline.End() 67 | } 68 | catch 69 | { 70 | throw 71 | } 72 | }#end end 73 | 74 | } 75 | -------------------------------------------------------------------------------- /scripts/EdgeChrProfiles.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param( 3 | [parameter(Mandatory)] 4 | [ValidateScript( { Test-Path -Path $_ -PathType Container })] 5 | [string]$OutputFolderPath 6 | ) 7 | $EdgeProfileDirectories = @(Get-ChildItem "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Profile *" -Directory; Get-Item -LiteralPath "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\") 8 | 9 | $EdgeProfiles = $EdgeProfileDirectories.fullname.foreach( { 10 | $ProfilePath = $_ 11 | $PreferencesFilePath = Join-Path -Path $ProfilePath -ChildPath Preferences 12 | $ProfileChildPath = Split-Path -Path $ProfilePath -Leaf 13 | (ConvertFrom-Json (Get-Content $PreferencesFilePath -Raw)) | Select-Object -Property *, @{n = 'ProfilePath'; e = { $ProfilePath } }, @{n = 'ProfileChildPath'; e = { $ProfileChildPath } } 14 | }) 15 | 16 | $EdgeProfiles.foreach( { 17 | $p = $_ 18 | $ProfileName = $p.profile.name 19 | switch (Read-PromptForChoice -Choices "Yes", "No" -Title "Create Edge Profile Shortcut?" -Message "Do you want to create a shortcut for Edge Profile $ProfileName" -DefaultChoice 0) 20 | { 21 | 0 22 | { 23 | # Create Shortcut on Desktop 24 | 25 | $TargetPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" 26 | $ShortcutFile = Join-Path -Path $OutputFolderPath -ChildPath "$ProfileName.lnk" 27 | $WScriptShell = New-Object -ComObject WScript.Shell 28 | $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) 29 | $IconPath = Join-Path -Path $p.ProfilePath -ChildPath 'Edge Profile.ico' 30 | $Shortcut.IconLocation = "$IconPath, 0" 31 | $Shortcut.Arguments = "--profile-directory=""$($p.ProfileChildPath)""" 32 | $Shortcut.TargetPath = $TargetPath 33 | $Shortcut.Save() 34 | } 35 | 1 36 | { 37 | Write-Information -MessageData "Skipping Shortcut Creation for Profile $ProfileName" -InformationAction Continue 38 | } 39 | } 40 | }) -------------------------------------------------------------------------------- /scripts/launchtest.ps1: -------------------------------------------------------------------------------- 1 | function Start-SelectedApp 2 | { 3 | [CmdletBinding()] 4 | param ( 5 | # Name of the App to Start 6 | [Parameter(Mandatory,ParameterSetName='Name')] 7 | [String[]] 8 | $Name 9 | , 10 | # Name of the Group of Apps to Start 11 | [Parameter(Mandatory,ParameterSetName='Group')] 12 | [ValidateSet('DailyCrypto')] 13 | [String] 14 | $Group 15 | ) 16 | 17 | begin 18 | { 19 | 20 | } 21 | 22 | process 23 | { 24 | $AppsToStart = @( 25 | switch ($pscmdlet.ParameterSetName) 26 | { 27 | 'Name' 28 | { 29 | $Name 30 | } 31 | 'Group' 32 | { 33 | switch ($Group) 34 | { 35 | 'DailyCrypto' 36 | { 37 | 'MetaMask' 38 | 'AAVE' 39 | 'Adamant' 40 | 'Balancer' 41 | 'Mai Finance' 42 | 'Impermax' 43 | 'Cometh' 44 | 'QuickSwap' 45 | 'ParaSwap' 46 | 'Mantra DAO' 47 | 'Polygon Beefy' 48 | } 49 | } 50 | } 51 | } 52 | ) 53 | foreach ($a in $AppsToStart) 54 | { 55 | $appID = $(Get-StartApps -Name $a).AppID.tostring() 56 | $path = 'shell:AppsFolder\' + $appID 57 | 58 | Start-Process $path 59 | 60 | } 61 | } 62 | 63 | end 64 | { 65 | 66 | } 67 | } 68 | 69 | 70 | $MyApps = @( 71 | 'MetaMask' 72 | 'AAVE' 73 | 'Adamant' 74 | 'Balancer' 75 | 'Mai Finance' 76 | 'Impermax' 77 | 'Cometh' 78 | 'QuickSwap' 79 | 'ParaSwap' 80 | 'Yoroi' 81 | 'CoinList' 82 | 'Demex' 83 | 'DODO' 84 | 'Mantra DAO' 85 | 'Polygon Beefy' 86 | ) 87 | -------------------------------------------------------------------------------- /functions/Utilities/Get-SpecialFolder.ps1: -------------------------------------------------------------------------------- 1 | Function Get-SpecialFolder 2 | { 3 | <# 4 | Original source: https://github.com/gravejester/Communary.ConsoleExtensions/blob/master/Functions/Get-SpecialFolder.ps1 5 | MIT License 6 | Copyright (c) 2016 Øyvind Kallstad 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | #> 26 | [cmdletbinding(DefaultParameterSetName = 'All')] 27 | param ( 28 | [parameter(ValueFromPipeline)] 29 | [System.Environment+SpecialFolder[]]$Name = [Enum]::GetNames([System.Environment+SpecialFolder]) 30 | ) 31 | process 32 | { 33 | foreach ($folder in $Name) 34 | { 35 | $FolderObject = 36 | [PSCustomObject]@{ 37 | Name = $folder.ToString() 38 | Path = [System.Environment]::GetFolderPath($folder) 39 | } 40 | $FolderObject 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Module.Tests.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") 2 | $Script:ModuleRoot = $(Split-Path -Path $PSScriptRoot -Parent) 3 | Write-Information -MessageData "Module Root is $script:ModuleRoot" -InformationAction Continue 4 | $Script:ModuleName = $(Split-Path -$Script:ModuleRoot -Leaf) 5 | Write-Information -MessageData "Module Name is $Script:ModuleName" -InformationAction Continue 6 | $Script:ModuleFile = $Script:ModuleFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psm1') 7 | Write-Information -MessageData "Module File is $($script:ModuleFile)" -InformationAction Continue 8 | $Script:ModuleSettingsFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psd1') 9 | Write-Information -MessageData "Module Settings File is $($script:ModuleSettingsFile)" -InformationAction Continue 10 | #Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 11 | #Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue 12 | #Write-Information -MessageData "Import Module $Script:ModuleName" -InformationAction Continue 13 | #Import-Module -Name $Script:ModuleFullPath -Force 14 | 15 | Describe "$ModuleName Unit Tests" -Tag 'UnitTests' { 16 | Context "Validate Top Level Files" { 17 | [string[]]$moduleFileNames = (Get-ChildItem $ModuleRoot -File).Name 18 | $expectedFileNames = @($($ModuleName + '.psd1'), $($ModuleName + '.psm1'), 'README.md', 'license', 'ScriptAnalyzerSettings.psd1') 19 | It "Should contain expected files $($expectedFileNames -join ', ')" { 20 | ( (Compare-Object -ReferenceObject $expectedFileNames -DifferenceObject $moduleFileNames -IncludeEqual | Where-Object SideIndicator -eq "==").Count ) | Should Be $expectedFileNames.Count 21 | } 22 | <# It "Should only contain $paramCount parameters" { 23 | $params.Count - $defaultParamCount | Should Be $paramCount 24 | } #> 25 | } 26 | } 27 | 28 | #Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 29 | #Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue -------------------------------------------------------------------------------- /functions/AD/Get-ADRecipientObject.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ADRecipientObject { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [int]$ResultSetSize = 10000 7 | , 8 | [switch]$Passthrough 9 | , 10 | [switch]$ExportData 11 | , 12 | [parameter(Mandatory = $true)] 13 | $ADInstance 14 | ) 15 | Set-Location -Path "$($ADInstance):\" 16 | $AllGroups = Get-ADGroup -ResultSetSize $ResultSetSize -Properties @($AllADAttributesToRetrieve + 'Members') -Filter * | Select-Object -Property * -ExcludeProperty Property*, Item 17 | $AllMailEnabledGroups = $AllGroups | Where-Object -FilterScript {$_.legacyExchangeDN -ne $NULL -or $_.mailNickname -ne $NULL -or $_.proxyAddresses -ne $NULL} 18 | $AllContacts = Get-ADObject -Filter {objectclass -eq 'contact'} -Properties $AllADContactAttributesToRetrieve -ResultSetSize $ResultSetSize | Select-Object -Property * -ExcludeProperty Property*, Item 19 | $AllMailEnabledContacts = $AllContacts | Where-Object -FilterScript {$_.legacyExchangeDN -ne $NULL -or $_.mailNickname -ne $NULL -or $_.proxyAddresses -ne $NULL} 20 | $AllUsers = Get-ADUser -ResultSetSize $ResultSetSize -Filter * -Properties $AllADAttributesToRetrieve | Select-Object -Property * -ExcludeProperty Property*, Item 21 | $AllMailEnabledUsers = $AllUsers | Where-Object -FilterScript {$_.legacyExchangeDN -ne $NULL -or $_.mailNickname -ne $NULL -or $_.proxyAddresses -ne $NULL} 22 | $AllPublicFolders = Get-ADObject -Filter {objectclass -eq 'publicFolder'} -ResultSetSize $ResultSetSize -Properties $AllADAttributesToRetrieve | Select-Object -Property * -ExcludeProperty Property*, Item 23 | $AllMailEnabledPublicFolders = $AllPublicFolders | Where-Object -FilterScript {$_.legacyExchangeDN -ne $NULL -or $_.mailNickname -ne $NULL -or $_.proxyAddresses -ne $NULL} 24 | $AllMailEnabledADObjects = $AllMailEnabledGroups + $AllMailEnabledContacts + $AllMailEnabledUsers + $AllMailEnabledPublicFolders 25 | if ($Passthrough) {$AllMailEnabledADObjects} 26 | if ($ExportData) {Export-Data -DataToExport $AllMailEnabledADObjects -DataToExportTitle 'AllADRecipientObjects' -Depth 3 -DataType xml} 27 | 28 | } 29 | -------------------------------------------------------------------------------- /functions/AD/Get-ADAttributeSchema.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ADAttributeSchema { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [parameter(Mandatory = $true, ParameterSetName = 'LDAPDisplayName')] 7 | [string]$LDAPDisplayName 8 | , 9 | [parameter(Mandatory = $true, ParameterSetName = 'CommonName')] 10 | [string]$CommonName 11 | , 12 | [string[]]$properties = @() 13 | ) 14 | if (-not ((Test-ForInstalledModule -Name ActiveDirectory) -and (Test-ForImportedModule -Name ActiveDirectory))) 15 | {throw "Module ActiveDirectory must be installed and imported to use $($MyInvocation.MyCommand)."} 16 | if ((Get-ADDrive).count -lt 1) {throw "An ActiveDirectory PSDrive must be connected to use $($MyInvocation.MyCommand)."} 17 | try 18 | { 19 | if (-not (Test-Path -path variable:script:LoggedOnUserActiveDirectoryForest)) 20 | {$script:LoggedOnUserActiveDirectoryForest = Get-ADForest -Current LoggedOnUser -ErrorAction Stop} 21 | } 22 | catch 23 | { 24 | $_ 25 | throw 'Could not find AD Forest' 26 | } 27 | $schemalocation = "CN=Schema,$($script:LoggedOnUserActiveDirectoryForest.PartitionsContainer.split(',',2)[1])" 28 | $GetADObjectParams = @{ 29 | ErrorAction = 'Stop' 30 | } 31 | if ($properties.count -ge 1) {$GetADObjectParams.Properties = $properties} 32 | switch ($PSCmdlet.ParameterSetName) 33 | { 34 | 'LDAPDisplayName' 35 | { 36 | $GetADObjectParams.Filter = "lDAPDisplayName -eq `'$LDAPDisplayName`'" 37 | $GetADObjectParams.SearchBase = $schemalocation 38 | } 39 | 'CommonName' 40 | { 41 | $GetADObjectParams.Identity = "CN=$CommonName,$schemalocation" 42 | } 43 | } 44 | try 45 | { 46 | $ADObjects = @(Get-ADObject @GetADObjectParams) 47 | if ($ADObjects.Count -eq 0) 48 | {Write-Warning -Message "Failed: Find AD Attribute with name/Identifier: $($LDAPDisplayName,$GetADObjectParams.Identity)"} 49 | else 50 | { 51 | Write-Output -InputObject $ADObjects[0] 52 | } 53 | } 54 | catch 55 | { 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /scripts/BiblePlan/test.ps1: -------------------------------------------------------------------------------- 1 | # code for New-SplitArrayRange 2 | 3 | $prefixSum = [System.Collections.Generic.List[decimal]]::new() 4 | $e = 0 5 | $NTC.foreach({ #$inputArray 6 | $prefixSum.add($_.WordCount + $prefixSum[$e - 1]) #$NumericValueAttribute 7 | $e++ 8 | }) 9 | 10 | $uff = .32 11 | $lff = .745 12 | $limit = 1400 13 | $upperlimit = $limit * $(1 + $uff) 14 | $lowerlimit = $limit * $(1 - $lff) 15 | $lastsum = 0 16 | for ( 17 | $i,$p,$start = 0,1,0 18 | $i -lt $NTC.count 19 | $i++ 20 | ) 21 | { 22 | $tempsum = $prefixSum[$i] - $lastsum 23 | switch ($tempsum) 24 | { 25 | {$tempsum -le $upperlimit -and $tempsum -ge $lowerlimit} # in range 26 | # lessthan or equal to the limit plus the fudge factor percentage 27 | # AND greater than or equal to the limit minus the fudge factor percentage 28 | { 29 | [PSCustomObject]@{ 30 | Part = $p 31 | Start = $start 32 | End = $i 33 | Sum = $tempsum 34 | } 35 | $lastsum = $prefixSum[$i] # record lastsum which is current element 36 | $start = $i + 1 # next start ready which will be next element 37 | $p++ # ready for next part 38 | } 39 | {$tempsum -gt $upperlimit} # above range - fall back to last element 40 | { 41 | 42 | [PSCustomObject]@{ 43 | Part = $p 44 | Start = $start 45 | End = $i -1 46 | Sum = $prefixSum[$i-1] - $lastsum 47 | } 48 | $lastsum = $prefixSum[$i-1] # record lastsum which is last element 49 | $start = $i # next start ready which will be current element 50 | $p++ # ready for next part 51 | } 52 | {$tempsum -lt $lowerlimit} # below range - usually continue to next element 53 | { 54 | if ($i -eq $($otc.count -1)) 55 | { 56 | [PSCustomObject]@{ 57 | Part = $p 58 | Start = $start 59 | End = $i 60 | Sum = $tempsum 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /functions/Exchange/Get-DuplicateEmailAddresses.ps1: -------------------------------------------------------------------------------- 1 | Function Get-DuplicateEmailAddresses { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory)] 6 | $ExchangeOrganization 7 | ) 8 | Write-Verbose -Message "Building Exchange Proxy Address Hashtable with New-TestExchangeProxyAddress" 9 | New-TestExchangeProxyAddress -ExchangeOrganization $ExchangeOrganization 10 | #$TestExchangeProxyAddress = Get-OneShellVariableValue -Name TestExchangeProxyAddress 11 | Write-Verbose -Message "Filtering Exchange Proxy Address Hashtable for Addresses Assigned to Multiple Recipients" 12 | $duplicateAddresses = $TestExchangeProxyAddress.GetEnumerator() | Where-Object -FilterScript {$_.Value.count -gt 1} 13 | Write-Verbose -Message "Iterating through duplicate addresses and creating output" 14 | $duplicatnum = 0 15 | foreach ($dup in $duplicateAddresses) 16 | { 17 | $duplicatnum++ 18 | foreach ($val in $dup.value) 19 | { 20 | $splat = @{ 21 | cmdlet = 'get-recipient' 22 | ExchangeOrganization = $ExchangeOrganization 23 | ErrorAction = 'Stop' 24 | splat = @{ 25 | Identity = $val 26 | ErrorAction = 'Stop' 27 | }#innersplat 28 | }#outersplat 29 | try 30 | { 31 | $Recipient = Invoke-ExchangeCommand @splat 32 | }#try 33 | catch 34 | { 35 | $message = "Get-Recipient $val in Exchange Organization $ExchangeOrganization" 36 | Write-Log -Message $message -EntryType Failed -ErrorLog 37 | }#catch 38 | $duplicateobject = [pscustomobject]@{ 39 | DuplicateAddress = $dup.Name 40 | DuplicateNumber = $duplicatnum 41 | DuplicateRecipientCount = $dup.Value.Count 42 | RecipientDN = $Recipient.distinguishedName 43 | RecipientAlias = $recipient.alias 44 | RecipientPrimarySMTPAddress = $recipient.primarysmtpaddress 45 | RecipientGUID = $Recipient.guid 46 | RecipientTypeDetails = $Recipient.RecipientTypeDetails 47 | } 48 | $duplicateobject 49 | }#Foreach 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /functions/DateTime/New-PomoTimer.ps1: -------------------------------------------------------------------------------- 1 | Function New-PomoTimer 2 | { 3 | [cmdletbinding()] 4 | param( 5 | $PomoMinutes = 25 6 | , 7 | $BreakMinutes = 15 8 | ) 9 | 10 | 11 | $TotalPomos = 0 12 | $SinceBreak = 0 13 | $TotalBreaks = 0 14 | $SinceBreak = 0 15 | 16 | $userChoiceParams = @{ 17 | Title = 'Select Next Action' 18 | Choices = @('Pomo','Break','Quit') 19 | Message = '' 20 | Numbered = $true 21 | } 22 | $userChoice = 'Pomo' 23 | 24 | Do 25 | { 26 | switch ($userchoice) 27 | { 28 | 'Pomo' 29 | { 30 | $Length = $PomoMinutes 31 | $TotalPomos++ 32 | $SinceBreak++ 33 | } 34 | 'Break' 35 | { 36 | $Length = $BreakMinutes 37 | $TotalBreaks++ 38 | $SinceBreak=0 39 | } 40 | } 41 | $newTimerParams = @{ 42 | units = 'Minutes' 43 | length = $Length 44 | showprogress = $true 45 | speech = $true 46 | Frequency = 5 47 | delay = 10 48 | AltReport = @( 49 | @{ 50 | Units = 'Minutes' 51 | Frequency = 1 52 | Countdownpoint = 3 53 | } 54 | @{ 55 | Units = 'Seconds' 56 | Frequency = 5 57 | Countdownpoint = 60 58 | } 59 | @{ 60 | Units = 'Seconds' 61 | Frequency = 1 62 | Countdownpoint = 10 63 | } 64 | ) 65 | } 66 | 67 | New-Timer @newTimerParams 68 | 69 | $oldChoice = $userChoice 70 | $userChoice = $null 71 | 72 | Do 73 | { 74 | Out-Speech -InputObject "$oldChoice has ended. Select the next activity." -ConfigurationName 'Timer' 75 | $userChoiceParams.Message = "Total Pomo: $TotalPomos, Pomo Since Last Break: $SinceBreak, Total Breaks: $TotalBreaks" 76 | $UserChoice = $userChoiceParams.Choices[$(Read-PromptForChoice @userChoiceParams)] 77 | } 78 | Until ( $null -ne $userChoice ) 79 | 80 | } 81 | until ( 82 | $UserChoice -eq 'quit' 83 | ) 84 | 85 | } 86 | -------------------------------------------------------------------------------- /functions/Utilities/Get-MaxLengthOfAllAttributes.ps1: -------------------------------------------------------------------------------- 1 | function Get-MaxLengthOfAllAttributes 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [switch]$ShowProgress 6 | , 7 | [parameter(ValueFromPipeline)] 8 | [psobject[]]$InputObject 9 | ) 10 | begin 11 | { 12 | $AllPropertyMaxLengths = @{} 13 | if ($ShowProgress -and $PSBoundParameters.Keys.Contains('InputObject')) 14 | { 15 | $TotalCount = $InputObject.Count 16 | $i = 0 17 | $CountKnown = $true 18 | } 19 | else 20 | { 21 | $TotalCount = 0 22 | $CountKnown = $false 23 | } 24 | $WriteProgressParams = @{ 25 | Activity = 'Analyzing Objects' 26 | Status = "$i of $totalCount" 27 | PercentComplete = 0 28 | } 29 | }#begin 30 | process 31 | { 32 | foreach ($o in $InputObject) 33 | { 34 | if ($ShowProgress) 35 | { 36 | $i++ 37 | if ($CountKnown -eq $false) 38 | { 39 | $TotalCount++ 40 | } 41 | $WriteProgressParams.Status = "$i of $totalcount" 42 | $WriteProgressParams.PercentComplete = $i/$TotalCount*100 43 | Write-Progress @WriteProgressParams 44 | } 45 | $OPropertyList = $o | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name 46 | foreach ($p in $OPropertyList) 47 | { 48 | if ($o.$p.capacity -ne $null) 49 | { 50 | $length = @($o.$p.Length) | Measure-Object -Sum | Select-Object -ExpandProperty Sum 51 | } 52 | else 53 | { 54 | $length = $o.$p.Length 55 | } 56 | if ($AllPropertyMaxLengths.ContainsKey($p)) 57 | { 58 | if ($length -gt $AllPropertyMaxLengths.$p) 59 | { 60 | $AllPropertyMaxLengths.$p = $($length) 61 | } 62 | } 63 | else 64 | { 65 | $AllPropertyMaxLengths.$p = $length 66 | } 67 | } 68 | } 69 | }#process 70 | end 71 | { 72 | $AllPropertyMaxLengths.GetEnumerator() | Sort-Object -Property Name 73 | }#end 74 | } 75 | -------------------------------------------------------------------------------- /promptThemes/MC.psm1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 -Modules posh-git 2 | 3 | function Write-Theme 4 | { 5 | param( 6 | [bool] 7 | $lastCommandFailed, 8 | [string] 9 | $with 10 | ) 11 | 12 | #check the last command state and indicate if failed 13 | If ($lastCommandFailed) 14 | { 15 | $prompt = Write-Prompt -Object "$($sl.PromptSymbols.FailedCommandSymbol) " -ForegroundColor $sl.Colors.CommandFailedIconForegroundColor 16 | } 17 | 18 | #check for elevated prompt 19 | If (Test-Administrator) 20 | { 21 | $prompt += Write-Prompt -Object "$($sl.PromptSymbols.ElevatedSymbol) " -ForegroundColor $sl.Colors.AdminIconForegroundColor 22 | } 23 | 24 | $user = $sl.CurrentUser 25 | if (Test-NotDefaultUser($user)) 26 | { 27 | $prompt += Write-Prompt -Object "$user " -ForegroundColor $sl.Colors.PromptForegroundColor 28 | } 29 | 30 | # Writes the drive portion 31 | $prompt += Write-Prompt -Object "$(Get-ShortPath -dir $pwd) " -ForegroundColor $sl.Colors.DriveForegroundColor 32 | 33 | $status = Get-VCSStatus 34 | if ($status) 35 | { 36 | $themeInfo = Get-VcsInfo -status ($status) 37 | $prompt += Write-Prompt -Object "git:" -ForegroundColor $sl.Colors.PromptForegroundColor 38 | $prompt += Write-Prompt -Object "$($themeInfo.VcInfo) " -ForegroundColor $themeInfo.BackgroundColor 39 | } 40 | 41 | if ($with) 42 | { 43 | $prompt += Write-Prompt -Object "$($with.ToUpper()) " -BackgroundColor $sl.Colors.WithBackgroundColor -ForegroundColor $sl.Colors.WithForegroundColor 44 | } 45 | 46 | # Writes the postfixes to the prompt 47 | $prompt += Write-Prompt -Object $sl.PromptSymbols.PromptIndicator -ForegroundColor $sl.Colors.CommandFailedIconForegroundColor 48 | $prompt += Write-Prompt -Object $sl.PromptSymbols.PromptIndicator -ForegroundColor $sl.Colors.AdminIconForegroundColor 49 | $prompt += Write-Prompt -Object $sl.PromptSymbols.PromptIndicator -ForegroundColor $sl.Colors.GitNoLocalChangesAndAheadColor 50 | $prompt += ' ' 51 | $prompt 52 | } 53 | 54 | $sl = $global:ThemeSettings #local settings 55 | $sl.PromptSymbols.PromptIndicator = [char]::ConvertFromUtf32(0x276F) 56 | $sl.Colors.PromptForegroundColor = [ConsoleColor]::White 57 | $sl.Colors.PromptSymbolColor = [ConsoleColor]::White 58 | $sl.Colors.PromptHighlightColor = [ConsoleColor]::DarkBlue 59 | $sl.Colors.WithForegroundColor = [ConsoleColor]::DarkRed 60 | $sl.Colors.WithBackgroundColor = [ConsoleColor]::Magenta 61 | -------------------------------------------------------------------------------- /tests/Help.Tests.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") 2 | $Script:ModuleRoot = $(Split-Path -Path $PSScriptRoot -Parent) 3 | Write-Information -MessageData "Module Root is $script:ModuleRoot" -InformationAction Continue 4 | $Script:ModuleName = $(Split-Path -$Script:ModuleRoot -Leaf) 5 | Write-Information -MessageData "Module Name is $Script:ModuleName" -InformationAction Continue 6 | $Script:ModuleFile = $Script:ModuleFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psm1') 7 | Write-Information -MessageData "Module File is $($script:ModuleFile)" -InformationAction Continue 8 | $Script:ModuleSettingsFile = Join-Path -Path $($Script:ModuleRoot) -ChildPath $($Script:ModuleName + '.psd1') 9 | Write-Information -MessageData "Module Settings File is $($script:ModuleSettingsFile)" -InformationAction Continue 10 | 11 | Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 12 | Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue 13 | Write-Information -MessageData "Import Module $Script:ModuleName" -InformationAction Continue 14 | Import-Module -Name $Script:ModuleSettingsFile -Force 15 | 16 | Describe "Public commands in $script:ModuleName have comment-based or external help" -Tags 'Build' { 17 | $functions = Get-Command -Module $Script:ModuleName 18 | $help = foreach ($function in $functions) 19 | { 20 | Get-Help -Name $function.Name 21 | } 22 | 23 | foreach ($node in $help) 24 | { 25 | Context $node.Name { 26 | It "Should have a Description or Synopsis" { 27 | ($node.Description + $node.Synopsis) | Should Not BeNullOrEmpty 28 | } 29 | 30 | It "Should have an Example" { 31 | $node.Examples | Should Not BeNullOrEmpty 32 | $node.Examples | Out-String | Should -Match ($node.Name) 33 | } 34 | 35 | foreach ($parameter in $node.Parameters.Parameter) 36 | { 37 | if ($parameter -notmatch 'WhatIf|Confirm') 38 | { 39 | It "Should have a Description for Parameter [$($parameter.Name)]" { 40 | $parameter.Description.Text | Should Not BeNullOrEmpty 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | Write-Information -MessageData "Removing Module $Script:ModuleName" -InformationAction Continue 49 | Remove-Module -Name $Script:ModuleName -Force -ErrorAction SilentlyContinue -------------------------------------------------------------------------------- /functions/Utilities/Add-MediaFileDateTakenAttribute.ps1: -------------------------------------------------------------------------------- 1 | function Add-MediaFileDateTakenAttribute 2 | { 3 | <# 4 | .SYNOPSIS 5 | Adds an attribute to the file(s) specified with the DateTaken information extracted from exif data in the file, if available. If no DateTaken date is found DateTaken will be added with a NULL value. 6 | .DESCRIPTION 7 | A longer description of the function, its purpose, common use cases, etc. 8 | .NOTES 9 | Probably not supported in linux (uses ComObject Shell.Application) 10 | .EXAMPLE 11 | Test-MyTestFunction -Verbose 12 | Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines 13 | #> 14 | 15 | 16 | [cmdletbinding()] 17 | param( 18 | [ValidateScript({Test-Path -Path $_ -PathType Leaf})] 19 | [string[]]$FilePath 20 | ) 21 | begin 22 | { 23 | $ShellExp = New-Object -ComObject Shell.Application 24 | } 25 | process 26 | { 27 | foreach ($i in $FilePath) 28 | { 29 | Write-Information -MessageData "Processing item $i" 30 | $item = Get-Item -Path $i 31 | Write-Information -MessageData "Processing item directory $($item.DirectoryName)" 32 | $itemdirectory = $ShellExp.NameSpace($item.DirectoryName) 33 | $itemfile = $itemdirectory.ParseName($item.name) 34 | $datetaken = $null 35 | Write-Information -MessageData "Processing item extention type $($item.Extension)" 36 | switch ($item.Extension) 37 | { 38 | '.mov' 39 | { 40 | $datetakenstring = ($itemdirectory.GetDetailsOf($itemfile, 208) -replace "`u{200e}") -replace "`u{200f}" 41 | } 42 | '.avi' 43 | { 44 | $datetakenstring = ($itemdirectory.GetDetailsOf($itemfile, 208) -replace "`u{200e}") -replace "`u{200f}" 45 | } 46 | '.jpg' 47 | { 48 | $datetakenstring = ($itemdirectory.GetDetailsOf($itemfile, 12) -replace "`u{200e}") -replace "`u{200f}" 49 | } 50 | } 51 | 52 | if (-not [string]::IsNullOrEmpty($datetakenstring)) 53 | { 54 | Write-Information -MessageData "Date Taken Value Identified: $datetakenstring" 55 | $dateTaken = $datetakenstring | Get-Date 56 | } 57 | Write-Information -MessageData "Adding DateTaken Attribute with value: $dateTaken" 58 | Add-Member -InputObject $item -NotePropertyName DateTaken -NotePropertyValue $Datetaken -PassThru 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /functions/ManagedInstalls/Update-LocalGitRepo.ps1: -------------------------------------------------------------------------------- 1 | Function Update-GitSourcedModule { 2 | 3 | [cmdletbinding()] 4 | param( 5 | $path = $(Get-Location).path 6 | , 7 | [switch]$Recurse 8 | ) 9 | $GetGitSourcedModuleParams = @{ 10 | path = $path 11 | } 12 | if ($true -eq $Recurse) 13 | { 14 | $GetGitSourcedModuleParams.Recurse = $true 15 | } 16 | $GitSourcedModules = @(Get-GitSourcedModule @GetGitSourcedModuleParams) 17 | if ($GitSourcedModules.Count -ge 1) 18 | { 19 | Push-Location 20 | foreach ($gsm in $GitSourcedModules) 21 | { 22 | Set-Location -LiteralPath $gsm 23 | $Name = Split-Path -Leaf -Path $gsm 24 | $GitStatus = Get-GitStatus | Select-Object -property @{n='Name';e={$Name}},Branch,Upstream,BehindBy,AheadBy,HasWorking 25 | Write-Information -MessageData $GitStatus 26 | $Message = "Fetching $Name from $($GitStatus.Upstream)" 27 | Write-Information -MessageData $Message 28 | git fetch 29 | $GitStatus = Get-GitStatus 30 | switch ($GitStatus.HasWorking) 31 | { 32 | $true 33 | { 34 | $Message = "$($Name) has $($gitStatus.Working.Count) file(s) with local uncommitted changes: $($GitStatus.Working -join ', ')" 35 | Write-Information -MessageData $Message -Tags Warning 36 | } 37 | $false 38 | { 39 | if ($GitStatus.BehindBy -gt 0) 40 | { 41 | $Message = "Pull $($Name) for $($GitStatus.Branch) from $($GitStatus.Upstream)" 42 | Write-Information -MessageData $Message 43 | git pull 44 | } 45 | if ($GitStatus.AheadBy -gt 0) 46 | { 47 | $Message = "Push $($Name) for $($GitStatus.Branch) to $($GitStatus.Upstream)" 48 | Write-Information -MessageData $Message 49 | git push 50 | } 51 | if ($GitStatus.BehindBy -eq 0 -and $gitStatus.AheadBy -eq 0) 52 | { 53 | $Message = "$Name is even with $($GitStatus.Upstream)" 54 | Write-Information -MessageData $Message 55 | } 56 | } 57 | } 58 | $GitStatus = Get-GitStatus | Select-Object -property @{n='Name';e={$Name}},Branch,Upstream,BehindBy,AheadBy,HasWorking 59 | Write-Information -MessageData $GitStatus 60 | } 61 | Pop-Location 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /functions/Word/New-WordTable.ps1: -------------------------------------------------------------------------------- 1 | Function New-WordTable 2 | { 3 | [cmdletbinding( 4 | DefaultParameterSetName='Table' 5 | )] 6 | Param ( 7 | [parameter()] 8 | [object]$WordObject, 9 | [parameter()] 10 | [object]$Object, 11 | [parameter()] 12 | [int]$Columns, 13 | [parameter()] 14 | [int]$Rows, 15 | [parameter(ParameterSetName='Table')] 16 | [switch]$AsTable, 17 | [parameter(ParameterSetName='List')] 18 | [switch]$AsList, 19 | [parameter()] 20 | [string]$TableStyle, 21 | [parameter()] 22 | [Microsoft.Office.Interop.Word.WdDefaultTableBehavior]$TableBehavior = 'wdWord9TableBehavior', 23 | [parameter()] 24 | [Microsoft.Office.Interop.Word.WdAutoFitBehavior]$AutoFitBehavior = 'wdAutoFitContent' 25 | ) 26 | #Specifying 0 index ensures we get accurate data from a single object 27 | $Properties = $Object[0].psobject.properties.name 28 | $Range = @($Word.Selection.Paragraphs)[-1].Range 29 | $Table = $WordObject.Selection.Tables.add($Range, $Rows, $Columns, $TableBehavior, $AutoFitBehavior) 30 | 31 | Switch ($PSCmdlet.ParameterSetName) 32 | { 33 | 'Table' 34 | { 35 | If (-NOT $PSBoundParameters.ContainsKey('TableStyle')) 36 | { 37 | $Table.Style = 'Medium Shading 1 - Accent 1' 38 | } 39 | $c = 1 40 | $r = 1 41 | #Build header 42 | $Properties | ForEach-Object { 43 | Write-Verbose "Adding $($_)" 44 | $Table.cell($r, $c).range.Bold=1 45 | $Table.cell($r, $c).range.text = $_ 46 | $c++ 47 | } 48 | $c = 1 49 | #Add Data 50 | For ($i=0; $i -lt (($Object | Measure-Object).Count); $i++) 51 | { 52 | $Properties | ForEach-Object { 53 | $Table.cell(($i+2), $c).range.Bold=0 54 | $Table.cell(($i+2), $c).range.text = $Object[$i].$_ 55 | $c++ 56 | } 57 | $c = 1 58 | } 59 | } 60 | 'List' 61 | { 62 | If (-NOT $PSBoundParameters.ContainsKey('TableStyle')) 63 | { 64 | $Table.Style = 'Light Shading - Accent 1' 65 | } 66 | $c = 1 67 | $r = 1 68 | $Properties | ForEach-Object { 69 | $Table.cell($r, $c).range.Bold=1 70 | $Table.cell($r, $c).range.text = $_ 71 | $c++ 72 | $Table.cell($r, $c).range.Bold=0 73 | $Table.cell($r, $c).range.text = $Object.$_ 74 | $c-- 75 | $r++ 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /functions/MSOL/Get-MsolUserLicenseDetail.ps1: -------------------------------------------------------------------------------- 1 | Function Get-MsolUserLicenseDetail 2 | { 3 | 4 | [cmdletbinding()] 5 | param( 6 | [parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'UserPrincipalName')] 7 | [string[]]$UserPrincipalName 8 | , 9 | [parameter(ValueFromPipeline = $true, ParameterSetName = 'MSOLUserObject')] 10 | #[Microsoft.Online.Administration.User[]] 11 | $msoluser 12 | ) 13 | begin 14 | { 15 | function getresult 16 | { 17 | param($user) 18 | $result += [pscustomobject]@{ 19 | UserPrincipalName = $user.UserPrincipalName 20 | LicenseAssigned = $user.Licenses.AccountSKUID 21 | EnabledServices = @($user.Licenses.servicestatus | Select-Object -Property @{n = 'Service'; e = { $_.serviceplan.servicename } }, @{n = 'Status'; e = { $_.provisioningstatus } } | Where-Object Status -ne 'Disabled' | Select-Object -ExpandProperty Service) 22 | DisabledServices = @($user.Licenses.servicestatus | Select-Object -Property @{n = 'Service'; e = { $_.serviceplan.servicename } }, @{n = 'Status'; e = { $_.provisioningstatus } } | Where-Object Status -eq 'Disabled' | Select-Object -ExpandProperty Service) 23 | UsageLocation = $user.UsageLocation 24 | LicenseReconciliationNeeded = $user.LicenseReconciliationNeeded 25 | }#result 26 | $result 27 | } 28 | }#begin 29 | process 30 | { 31 | switch ($PSCmdlet.ParameterSetName) 32 | { 33 | 'UserPrincipalName' 34 | { 35 | foreach ($UPN in $UserPrincipalName) 36 | { 37 | try 38 | { 39 | Write-Log -Message "Attempting: Get-MsolUser for UserPrincipalName $UPN" 40 | $user = Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop 41 | Write-Log -Message "Succeeded: Get-MsolUser for UserPrincipalName $UPN" 42 | getresult -user $user 43 | }#try 44 | catch 45 | { 46 | Write-Log -message "Unable to locate MSOL User with UserPrincipalName $UPN" -ErrorLog 47 | Write-Log -message $_.tostring() -ErrorLog 48 | }#catch 49 | 50 | }#foreach 51 | }#UserPrincipalName 52 | 'MSOLUserObject' 53 | { 54 | foreach ($user in $msoluser) 55 | { 56 | getresult -user $user 57 | }#foreach 58 | }#MSOLUserObject 59 | }#switch 60 | }#process 61 | end 62 | { 63 | }#end 64 | 65 | } 66 | -------------------------------------------------------------------------------- /scripts/BibleAPIParams.ps1: -------------------------------------------------------------------------------- 1 | Optional Parameters 2 | Name: include-passage-references 3 | Type: boolean 4 | Default: true 5 | Include the passage reference before the text. 6 | 7 | Name: include-verse-numbers 8 | Type: boolean 9 | Default: true 10 | Include verse numbers. 11 | 12 | Name: include-first-verse-numbers 13 | Type: boolean 14 | Default: true 15 | Include the verse number for the first verse of a chapter. 16 | 17 | Name: include-footnotes 18 | Type: boolean 19 | Default: true 20 | Include callouts to footnotes in the text. 21 | 22 | Name: include-footnote-body 23 | Type: boolean 24 | Default: true 25 | Include footnote bodies below the text. Only works if include-footnotes is also true. 26 | 27 | Name: include-headings 28 | Type: boolean 29 | Default: true 30 | Include section headings. For example, the section heading of Matthew 5 is 'The Sermon on the Mount'. 31 | 32 | Name: include-short-copyright 33 | Type: boolean 34 | Default: true 35 | Include '(ESV)' at the end of the text. Mutually exclusive with include-copyright. This fulfills your copyright display requirements. 36 | 37 | Name: include-copyright 38 | Type: boolean 39 | Default: false 40 | Include a copyright notice at the end of the text. Mutually exclusive with include-short-copyright. This fulfills your copyright display requirements. 41 | 42 | Name: include-passage-horizontal-lines 43 | Type: boolean 44 | Default: false 45 | Include a line of equal signs (====) above the beginning of each passage. 46 | 47 | Name: include-heading-horizontal-lines 48 | Type: boolean 49 | Default: false 50 | Include a visual line of underscores (____) above each section heading. 51 | 52 | Name: horizontal-line-length 53 | Type: integer 54 | Default: 55 55 | Controls the length of the line for include-passage-horizontal-lines and include-heading-horizontal-lines. 56 | 57 | Name: include-selahs 58 | Type: boolean 59 | Default: true 60 | Include 'Selah' in certain Psalms. 61 | 62 | Name: indent-using 63 | Type: string 64 | Default: space 65 | Controls indentation. Must be space or tab. 66 | 67 | Name: indent-paragraphs 68 | Type: integer 69 | Default: 2 70 | Controls how many indentation characters start a paragraph. 71 | 72 | Name: indent-poetry 73 | Type: boolean 74 | Default: true 75 | Controls indentation of poetry lines. 76 | 77 | Name: indent-poetry-lines 78 | Type: integer 79 | Default: 4 80 | Controls how many indentation characters are used per indentation level for poetry lines. 81 | 82 | Name: indent-declares 83 | Type: integer 84 | Default: 40 85 | Controls how many indentation characters are used for 'Declares the LORD' in some of the prophets. 86 | 87 | Name: indent-psalm-doxology 88 | Type: integer 89 | Default: 30 90 | Controls how many indentation characters are used for Psalm doxologies. 91 | 92 | Name: line-length 93 | Type: integer 94 | Default: 0 95 | Controls how long a line can be before it is wrapped. Use 0 for unlimited line lengths. -------------------------------------------------------------------------------- /functions/Exchange/Test-ExchangeProxyAddress.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ExchangeProxyAddress { 2 | 3 | [cmdletbinding()] 4 | param 5 | ( 6 | [string]$ProxyAddress 7 | , 8 | [string[]]$ExemptObjectGUIDs 9 | , 10 | [switch]$ReturnConflicts 11 | , 12 | [parameter()] 13 | [System.Management.Automation.Runspaces.PSSession]$ExchangeSession 14 | , 15 | [parameter()] 16 | [ValidateSet('SMTP', 'X500')] 17 | [string]$ProxyAddressType = 'SMTP' 18 | ) 19 | #Populate the Global TestExchangeProxyAddress Hash Table if needed 20 | <# if (Test-Path -Path variable:Script:TestExchangeProxyAddress) 21 | { 22 | if ($RefreshProxyAddressData) 23 | { 24 | if ($null -eq $ExchangeSession) 25 | { 26 | throw('You must include the Exchange Session to use the RefreshProxyAddressData switch') 27 | } 28 | Write-Log -message 'Running New-TestExchangeProxyAddress' 29 | New-TestExchangeProxyAddress -ExchangeSession $ExchangeSession 30 | } 31 | } 32 | else 33 | { 34 | Write-Log -message 'Running New-TestExchangeProxyAddress' 35 | New-TestExchangeProxyAddress -ExchangeSession $ExchangeSession 36 | } 37 | #> 38 | 39 | #Fix the ProxyAddress if needed 40 | if ($ProxyAddress -like "$($proxyaddresstype):*") 41 | { 42 | $ProxyAddress = $ProxyAddress.Split(':')[1] 43 | } 44 | #Test the ProxyAddress 45 | $ReturnedObjects = @( 46 | try 47 | { 48 | invoke-command -Session $ExchangeSession -ScriptBlock {Get-Recipient -identity $using:ProxyAddress -ErrorAction Stop} -ErrorAction Stop 49 | Write-Verbose -Message "Existing object(s) Found for Alias $ProxyAddress" 50 | } 51 | catch 52 | { 53 | if ($_.categoryinfo -like '*ManagementObjectNotFoundException*') 54 | { 55 | Write-Verbose -Message "No existing object(s) Found for Alias $ProxyAddress" 56 | } 57 | else 58 | { 59 | throw($_) 60 | } 61 | } 62 | ) 63 | if ($ReturnedObjects.Count -ge 1) 64 | { 65 | $ConflictingGUIDs = @($ReturnedObjects | ForEach-Object {$_.guid.guid} | Where-Object {$_ -notin $ExemptObjectGUIDs}) 66 | if ($ConflictingGUIDs.count -gt 0) 67 | { 68 | if ($ReturnConflicts) 69 | { 70 | Return $ConflictingGUIDs 71 | } 72 | else 73 | { 74 | $false 75 | } 76 | } 77 | else 78 | { 79 | $true 80 | } 81 | } 82 | else 83 | { 84 | $true 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /functions/AD/Get-XADUserPasswordExpirationDate.ps1: -------------------------------------------------------------------------------- 1 | Function Get-XADUserPasswordExpirationDate { 2 | 3 | 4 | Param ([Parameter(Mandatory, Position = 0, ValueFromPipeline, HelpMessage = 'Identity of the Account')] 5 | 6 | $accountIdentity) 7 | 8 | PROCESS 9 | { 10 | 11 | $accountObj = Get-ADUser -Identity $accountIdentity -Properties PasswordExpired, PasswordNeverExpires, PasswordLastSet 12 | 13 | if ($accountObj.PasswordExpired) 14 | { 15 | 16 | Write-Output -InputObject $('Password of account: ' + $accountObj.Name + ' already expired!') 17 | 18 | } 19 | else 20 | { 21 | 22 | if ($accountObj.PasswordNeverExpires) 23 | { 24 | 25 | Write-Output -InputObject $('Password of account: ' + $accountObj.Name + ' is set to never expires!') 26 | 27 | } 28 | else 29 | { 30 | 31 | $passwordSetDate = $accountObj.PasswordLastSet 32 | 33 | if ($passwordSetDate -eq $null) 34 | { 35 | 36 | Write-Output -InputObject $('Password of account: ' + $accountObj.Name + ' has never been set!') 37 | 38 | } 39 | else 40 | { 41 | 42 | $maxPasswordAgeTimeSpan = $null 43 | 44 | $dfl = (get-addomain).DomainMode 45 | 46 | if ($dfl -ge 3) 47 | { 48 | 49 | ## Greater than Windows2008 domain functional level 50 | 51 | $accountFGPP = Get-ADUserResultantPasswordPolicy $accountObj 52 | 53 | if ($accountFGPP -ne $null) 54 | { 55 | 56 | $maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge 57 | 58 | } 59 | else 60 | { 61 | 62 | $maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge 63 | 64 | } 65 | 66 | } 67 | else 68 | { 69 | 70 | $maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge 71 | 72 | } 73 | 74 | if ($maxPasswordAgeTimeSpan -eq $null -or $maxPasswordAgeTimeSpan.TotalMilliseconds -eq 0) 75 | { 76 | 77 | Write-Output -InputObject $('MaxPasswordAge is not set for the domain or is set to zero!') 78 | 79 | } 80 | else 81 | { 82 | 83 | Write-Output -InputObject $('Password of account: ' + $accountObj.Name + ' expires on: ' + ($passwordSetDate + $maxPasswordAgeTimeSpan)) 84 | 85 | } 86 | 87 | } 88 | 89 | } 90 | 91 | } 92 | 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /functions/Utilities/Start-ComplexJob.ps1: -------------------------------------------------------------------------------- 1 | Function Start-ComplexJob 2 | { 3 | <# 4 | .SYNOPSIS 5 | Helps Start Complex Background Jobs with many arguments and functions using Start-Job. 6 | .DESCRIPTION 7 | Helps Start Complex Background Jobs with many arguments and functions using Start-Job. 8 | The primary utility is to bring custom functions from the current session into the background job. 9 | A secondary utility is to formalize the input for creation complex background jobs by using a hashtable template and splatting. 10 | .PARAMETER Name 11 | The name of the background job which will be created. A string. 12 | .PARAMETER JobFunctions 13 | The name[s] of any local functions which you wish to export to the background job for use in the background job script. 14 | The definition of any function listed here is exported as part of the script block to the background job. 15 | .EXAMPLE 16 | $StartComplexJobParams = @{ 17 | jobfunctions = @( 18 | 'Connect-WAAD' 19 | ,'Get-TimeStamp' 20 | ,'Write-Log' 21 | ,'Write-EndFunctionStatus' 22 | ,'Write-StartFunctionStatus' 23 | ,'Export-Data' 24 | ,'Get-MatchingAzureADUsersAndExport' 25 | ) 26 | name = "MatchingAzureADUsersAndExport" 27 | arguments = @($SourceData,$SourceDataFolder,$LogPath,$ErrorLogPath,$OnlineCred) 28 | script = [scriptblock]{ 29 | $PSModuleAutoloadingPreference = "None" 30 | $sourcedata = $args[0] 31 | $sourcedatafolder = $args[1] 32 | $logpath = $args[2] 33 | $errorlogpath = $args[3] 34 | $credential = $args[4] 35 | Connect-WAAD -MSOnlineCred $credential 36 | Get-MatchingAzureADUsersAndExport 37 | } 38 | } 39 | Start-ComplexJob @StartComplexJobParams 40 | #> 41 | [cmdletbinding()] 42 | param 43 | ( 44 | [string]$Name 45 | , 46 | [string[]]$JobFunctions 47 | , 48 | [psobject[]]$Arguments 49 | , 50 | [string]$Script 51 | ) 52 | #build functions to initialize in job 53 | $JobFunctionsText = '' 54 | foreach ($Function in $JobFunctions) 55 | { 56 | $FunctionText = 'function ' + (Get-Command -Name $Function).Name + "{`r`n" + (Get-Command -Name $Function).Definition + "`r`n}`r`n" 57 | $JobFunctionsText = $JobFunctionsText + $FunctionText 58 | } 59 | $ExecutionScript = $JobFunctionsText + $Script 60 | #$initializationscript = [scriptblock]::Create($script) 61 | $ScriptBlock = [scriptblock]::Create($ExecutionScript) 62 | $StartJobParams = @{ 63 | Name = $Name 64 | ArgumentList = $Arguments 65 | ScriptBlock = $ScriptBlock 66 | } 67 | #$startjobparams.initializationscript = $initializationscript 68 | Start-Job @StartJobParams 69 | 70 | } 71 | -------------------------------------------------------------------------------- /functions/OneDrive/Get-OneDriveAccount.ps1: -------------------------------------------------------------------------------- 1 | Function Get-OneDriveAccount 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [parameter()] 6 | [AllowNull()] 7 | [AllowEmptyString()] 8 | [string[]]$DisplayName 9 | , 10 | [parameter()] 11 | [AllowNull()] 12 | [AllowEmptyString()] 13 | [string[]]$UserEmail 14 | , 15 | [parameter()] 16 | [AllowNull()] 17 | [guid[]]$TenantID 18 | ) 19 | if (Test-Path HKCU:\Software\Microsoft\OneDrive\Accounts -PathType Container) 20 | { 21 | @(Get-ChildItem -Path HKCU:\Software\Microsoft\OneDrive\Accounts).foreach( 22 | { Get-ItemProperty "Registry::$($_.name)" } 23 | ).where( 24 | { 25 | ( 26 | $null -eq $DisplayName -or [string]::IsNullOrEmpty($DisplayName) -or 27 | $_.DisplayName -in $DisplayName -or 28 | $( 29 | @(foreach ($dn in $DisplayName) 30 | { 31 | $_.DisplayName -like $dn 32 | }) -contains $true) 33 | ) -and 34 | ( 35 | $null -eq $userEmail -or [string]::IsNullOrEmpty($UserEmail) -or 36 | $_.UserEmail -in $UserEmail -or 37 | $( 38 | @(foreach ($ue in $UserEmail) 39 | { 40 | $_.UserEmail -like $ue 41 | }) -contains $true) 42 | ) -and 43 | ( 44 | $null -eq $TenantID -or 45 | $_.ConfiguredTenantId -in @($TenantID.foreach( { $_.guid })) 46 | ) 47 | } 48 | ).foreach( 49 | { 50 | $OutputObjectHash = [ordered]@{ } 51 | @($_.PSObject.properties | Sort-Object -Property Name).foreach( 52 | { 53 | $OutputObjectHash.$($_.Name) = $_.Value 54 | } 55 | ) 56 | $OutputObjectHash.RegistryPath = $OutputObjectHash.PSPath 57 | $ToRemove = $OutputObjectHash.Keys.where( { $_ -clike 'PS*' }) 58 | $ToRemove.foreach( { $OutputObjectHash.Remove($_) }) 59 | switch ($OutputObjectHash.Business) 60 | { 61 | 1 62 | { 63 | $OutputObjectHash.Insert(0, 'AccountType' , 'Business') 64 | } 65 | $null 66 | { 67 | $OutputObjectHash.Insert(0, 'AccountType' , 'Personal') 68 | } 69 | } 70 | New-Object -Property $OutputObjectHash -TypeName PSCustomObject 71 | } 72 | ).where( 73 | { $Null -ne $_.UserEmail } 74 | ) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tests/Get-RandomPassword.Tests.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") 2 | 3 | Describe "$CommandName Unit Tests" -Tag 'UnitTests' { 4 | Context "Validate parameters" { 5 | $defaultParamCount = 11 6 | [object[]]$params = (Get-ChildItem "function:\$CommandName").Parameters.Keys 7 | $knownParameters = 'Length','CharacterSet' 8 | $paramCount = $knownParameters.Count 9 | It "Should contain specific parameters" { 10 | ( (Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params -IncludeEqual | Where-Object SideIndicator -eq "==").Count ) | Should Be $paramCount 11 | } 12 | It "Should only contain $paramCount parameters" { 13 | $params.Count - $defaultParamCount | Should Be $paramCount 14 | } 15 | } 16 | } 17 | 18 | Describe "$commandname Integration Tests" -Tags "IntegrationTests" { 19 | Context "Validates Input" { 20 | It "Binds only values that are Int to the Length parameter" { 21 | {Get-RandomPassword -length 'two'} | Should Throw 22 | {Get-RandomPassword -Length 15} | Should NOT Throw 23 | } 24 | It "Binds only values that are in the validateSet to the CharacterSet parameter" { 25 | {Get-RandomPassword -CharacterSet 'Bob'} | Should Throw 26 | {Get-RandomPassword -CharacterSet 'Any'} | Should NOT Throw 27 | } 28 | } 29 | Context "Produces Expected Output" { 30 | $TestCases = @( 31 | @{ 32 | Length = 5 33 | CharacterSet = 'Any' 34 | } 35 | @{ 36 | Length = 15 37 | CharacterSet = 'Any' 38 | } 39 | @{ 40 | Length = 25 41 | CharacterSet = 'Any' 42 | } 43 | @{ 44 | Length = 5 45 | CharacterSet = 'NoSpecial' 46 | } 47 | @{ 48 | Length = 15 49 | CharacterSet = 'NoSpecial' 50 | } 51 | @{ 52 | Length = 25 53 | CharacterSet = 'NoSpecial' 54 | } 55 | ) 56 | It -TestCases $TestCases "Produces the expected password length" { 57 | param($Length,$CharacterSet) 58 | $result = Get-RandomPassword -Length $Length -CharacterSet $CharacterSet 59 | $($result).length | Should Be $Length 60 | } 61 | 62 | It -TestCases $TestCases "Produces the expected character content" { 63 | <# 64 | param($Length,$CharacterSet) 65 | $result = Get-RandomPassword -Length $Length -CharacterSet $CharacterSet 66 | switch ($CharacterSet) 67 | { 68 | 'Any' 69 | { 70 | $result 71 | } 72 | 'NoSpecial' 73 | { 74 | $result 75 | } 76 | } 77 | 78 | in#> 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /functions/Exchange/Test-RecipientObjectForUnwantedSMTPAddresses.ps1: -------------------------------------------------------------------------------- 1 | Function Test-RecipientObjectForUnwantedSMTPAddresses { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [Parameter(Mandatory)] 6 | [string[]]$WantedDomains 7 | , 8 | [Parameter(Mandatory)] 9 | [ValidateScript( {($_ | Test-Member -name 'EmailAddresses') -or ($_ | Test-Member -name 'ProxyAddresses')})] 10 | [psobject[]]$Recipient 11 | , 12 | [Parameter()] 13 | [ValidateSet('ReportUnwanted', 'ReportAll', 'TestOnly')] 14 | [string]$Operation = 'TestOnly' 15 | , 16 | [bool]$ValidateSMTPAddress = $true 17 | ) 18 | foreach ($R in $Recipient) 19 | { 20 | Switch ($R) 21 | { 22 | {$R | Test-Member -Name 'EmailAddresses'} 23 | {$AddrAtt = 'EmailAddresses'} 24 | {$R | Test-Member -Name 'ProxyAddresses'} 25 | {$AddrAtt = 'ProxyAddresses'} 26 | } 27 | $Addresses = @($R.$addrAtt) 28 | $TestedAddresses = @( 29 | foreach ($A in $Addresses) 30 | { 31 | if ($A -like 'smtp:*') 32 | { 33 | $RawA = $A.split(':')[1] 34 | $ADomain = $RawA.split('@')[1] 35 | $IsSupportedDomain = $ADomain -in $WantedDomains 36 | $outputRecord = 37 | [pscustomobject]@{ 38 | DistinguishedName = $R.DistinguishedName 39 | Identity = $R.Identity 40 | Address = $RawA 41 | Domain = $ADomain 42 | IsSupportedDomain = $IsSupportedDomain 43 | IsValidSMTPAddress = $null 44 | } 45 | if ($ValidateSMTPAddress) 46 | { 47 | $IsValidSMTPAddress = Test-EmailAddress -EmailAddress $RawA 48 | $outputRecord.IsValidSMTPAddress = $IsValidSMTPAddress 49 | } 50 | } 51 | $outputRecord 52 | } 53 | ) 54 | switch ($Operation) 55 | { 56 | 'TestOnly' 57 | { 58 | if ($TestedAddresses.IsSupportedDomain -contains $false -or $TestedAddresses.IsValidSMTPAddress -contains $false) 59 | {$false} 60 | else 61 | {$true} 62 | } 63 | 'ReportUnwanted' 64 | { 65 | $UnwantedAddresses = @($TestedAddresses | Where-Object -FilterScript {$_.IsSupportedDomain -eq $false -or $_.IsValidSMTPAddress -eq $false}) 66 | if ($UnwantedAddresses.Count -ge 1) 67 | { 68 | $UnwantedAddresses 69 | } 70 | } 71 | 'ReportAll' 72 | { 73 | $TestedAddresses 74 | } 75 | } 76 | }#foreach R in Recipient 77 | 78 | } 79 | -------------------------------------------------------------------------------- /functions/Utilities/Add-FunctionToPSSession.ps1: -------------------------------------------------------------------------------- 1 | Function Add-FunctionToPSSession { 2 | 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory)] 6 | [string[]]$FunctionNames 7 | , 8 | [parameter(ParameterSetName = 'SessionID', Mandatory, ValuefromPipelineByPropertyName)] 9 | [int]$ID 10 | , 11 | [parameter(ParameterSetName = 'SessionName', Mandatory, ValueFromPipelineByPropertyName)] 12 | [string]$Name 13 | , 14 | [parameter(ParameterSetName = 'SessionObject', Mandatory, ValueFromPipeline)] 15 | [Management.Automation.Runspaces.PSSession]$PSSession 16 | , 17 | [switch]$Refresh 18 | ) 19 | Write-Warning -Message "The Add-FunctionToPSSession should probably be updated to use AST for safer/more reliable operation. Use with Caution." -Verbose 20 | #Find the session 21 | $GetPSSessionParams = @{ 22 | ErrorAction = 'Stop' 23 | } 24 | switch ($PSCmdlet.ParameterSetName) 25 | { 26 | 'SessionID' 27 | { 28 | $GetPSSessionParams.ID = $ID 29 | $PSSession = Get-PSSession @GetPSSessionParams 30 | } 31 | 'SessionName' 32 | { 33 | $GetPSSessionParams.Name = $Name 34 | $PSSession = Get-PSSession @GetPSSessionParams 35 | } 36 | 'SessionObject' 37 | { 38 | #nothing required here 39 | } 40 | } 41 | #Verify the session availability 42 | if (-not $PSSession.Availability -eq 'Available') 43 | { 44 | throw "Availability Status for PSSession $($PSSession.Name) is $($PSSession.Availability). It must be Available." 45 | } 46 | #Verify if the functions already exist in the PSSession unless Refresh 47 | foreach ($FN in $FunctionNames) 48 | { 49 | $script = "Get-Command -Name '$FN' -ErrorAction SilentlyContinue" 50 | $scriptblock = [scriptblock]::Create($script) 51 | $remoteFunction = Invoke-Command -Session $PSSession -ScriptBlock $scriptblock -ErrorAction SilentlyContinue 52 | if ($null -ne $remoteFunction.CommandType -and -not $Refresh) 53 | { 54 | $FunctionNames = $FunctionNames | Where-Object -FilterScript {$_ -ne $FN} 55 | } 56 | } 57 | Write-Verbose -Message "Functions remaining: $($FunctionNames -join ',')" 58 | #Verify the local function availiability 59 | $Functions = @( 60 | foreach ($FN in $FunctionNames) 61 | { 62 | Get-Command -ErrorAction Stop -Name $FN -CommandType Function 63 | } 64 | ) 65 | #build functions text to initialize in PsSession 66 | $FunctionsText = '' 67 | foreach ($Function in $Functions) 68 | { 69 | $FunctionText = 'function ' + $Function.Name + "`r`n {`r`n" + $Function.Definition + "`r`n}`r`n" 70 | $FunctionsText = $FunctionsText + $FunctionText 71 | } 72 | #convert functions text to scriptblock 73 | $ScriptBlock = [scriptblock]::Create($FunctionsText) 74 | Invoke-Command -Session $PSSession -ScriptBlock $ScriptBlock -ErrorAction Stop 75 | 76 | } 77 | -------------------------------------------------------------------------------- /scripts/StaleUserManagement.md: -------------------------------------------------------------------------------- 1 | ## Initial Tenant Setup 2 | 3 | ### Register an Enterprise App 4 | 5 | Attribute | Value 6 | -- | -- 7 | Name | Stale Account Management 8 | Supported Account Types | Accounts in this organizational directory only 9 | 10 | Note the application (client) ID and the Directory (tenant) ID 11 | 12 | ### Add a client credential (certificate) to Enterprise App 13 | 14 | See Client Certificate Management 15 | 16 | ### Add required permissions to the Enterprise App 17 | 18 | The Stale User Accounts application requires the following permissions: 19 | 20 | - Sign in and read user profile (allows app sign in) 21 | - Read and write all users' full profiles (allows app to disable a user account) 22 | - Read all groups (allows app to view groups) 23 | - Read and write all group memberships (allows app to add and remove members to the application groups) 24 | - Read all audit log data (allows app to view last logon audit information) 25 | 26 | ### Add required security groups to Azure AD 27 | 28 | - A group to hold objects that have been disabled and are pending deletion for the DeleteAfterDays period 29 | - A group to hold objects granted an exception to automated deletion due to stale login activity 30 | - The objectID of each of these groups will be required for running to application function 31 | 32 | ## Client Certificate Management 33 | 34 | ### Requirements 35 | 36 | - Windows PowerShell 5.1 37 | - Microsoft's built-in PKI PowerShell Module 38 | - Elevated PowerShell 5.1 instance on the automation server 39 | - Access to Azure AD and permissions to manage the StaleCloudAccountsProcessing application Azure AD 40 | 41 | ### Steps 42 | 43 | #### Create certificate 44 | 45 | Suggested Values 46 | ```PowerShell 47 | $ValidYears = 1 48 | $ThroughDate = $(Get-Date).AddYears($ValidYears) 49 | $DNSName = '#####.onmicrosoft.com' # replace #### with your tenant's subdomain 50 | ``` 51 | 52 | ```PowerShell 53 | $Certificate = New-SelfSignedCertificate -DnsName $DNSName -CertStoreLocation "cert:\CurrentUser\My" -NotAfter $ThroughDate -KeySpec KeyExchange 54 | #OR 55 | "cert:\LocalMachine\My" -NotAfter $ThroughDate -KeySpec KeyExchange 56 | ``` 57 | 58 | #### Export certificate for upload to Azure AD 59 | 60 | ```PowerShell 61 | $Certificate | Export-Certificate -FilePath PublicCert.cer 62 | ``` 63 | 64 | #### Upload certificate to the StaleCloudAccountsProcessing application in Azure AD 65 | 66 | 67 | - Navigate to the application in Azure AD Portal and access Certificates & Secrets. 68 | - Click, 'Upload Certificate' and select and add the exported certificate file. 69 | - Verify the certificate thumbprint, start date, and expiration date 70 | 71 | #### Update the run script with the new certificate thumbprint 72 | 73 | - Update the value provided in the script for the CertificateThumbprint parameter. 74 | 75 | ### Backup the Certificate (if required) 76 | 77 | #### Export certificate to .pfx file with private key 78 | 79 | ```PowerShell 80 | $CertificateFullFilePath = 'c:\MyCert.cer' 81 | $Password = $(Get-Credential).Password 82 | $Certificate | Export-PfxCertificate -FilePath $CertificateFullFilePath -Password $Password 83 | ``` 84 | -------------------------------------------------------------------------------- /functions/Utilities/Out-FileUtf8NoBom.ps1: -------------------------------------------------------------------------------- 1 | Function Out-FileUtf8NoBom { 2 | 3 | #requires -version 3 4 | <# 5 | .SYNOPSIS 6 | Outputs to a UTF-8-encoded file *without a BOM* (byte-order mark). 7 | 8 | .DESCRIPTION 9 | Mimics the most important aspects of Out-File: 10 | * Input objects are sent to Out-String first. 11 | * -Append allows you to append to an existing file, -NoClobber prevents 12 | overwriting of an existing file. 13 | * -Width allows you to specify the line width for the text representations 14 | of input objects that aren't strings. 15 | However, it is not a complete implementation of all Out-String parameters: 16 | * Only a literal output path is supported, and only as a parameter. 17 | * -Force is not supported. 18 | 19 | Caveat: *All* pipeline input is buffered before writing output starts, 20 | but the string representations are generated and written to the target 21 | file one by one. 22 | 23 | .NOTES 24 | The raison d'eÌ‚tre for this advanced function is that, as of PowerShell v5, 25 | Out-File still lacks the ability to write UTF-8 files without a BOM: 26 | using -Encoding UTF8 invariably prepends a BOM. 27 | http://stackoverflow.com/questions/5596982/using-powershell-to-write-a-file-in-utf-8-without-the-bom 28 | #> 29 | [CmdletBinding()] 30 | param 31 | ( 32 | [Parameter(Mandatory, Position = 0)] 33 | [string] $LiteralPath 34 | , 35 | [switch] $Append 36 | , 37 | [switch] $NoClobber 38 | , 39 | [AllowNull()] [int] $Width 40 | , 41 | [Parameter(ValueFromPipeline)] 42 | $InputObject 43 | ) 44 | # Make sure that the .NET framework sees the same working dir. as PS 45 | # and resolve the input path to a full path. 46 | #[Environment]::CurrentDirectory = $PWD 47 | $LiteralPath = [IO.Path]::GetFullPath($LiteralPath) 48 | # If -NoClobber was specified, throw an exception if the target file already 49 | # exists. 50 | if ($NoClobber -and (Test-Path $LiteralPath)) 51 | { 52 | Throw [IO.IOException] "The file '$LiteralPath' already exists." 53 | } 54 | # Create a StreamWriter object. 55 | # Note that we take advantage of the fact that the StreamWriter class by default: 56 | # - uses UTF-8 encoding 57 | # - without a BOM. 58 | $sw = New-Object IO.StreamWriter $LiteralPath, $Append 59 | $htOutStringArgs = @{} 60 | if ($Width) 61 | { 62 | $htOutStringArgs += @{ Width = $Width } 63 | } 64 | # Note: By not using begin / process / end blocks, we're effectively running 65 | # in the end block, which means that all pipeline input has already 66 | # been collected in automatic variable $Input. 67 | # We must use this approach, because using | Out-String individually 68 | # in each iteration of a process block would format each input object 69 | # with an indvidual header. 70 | try 71 | { 72 | $InputObject | Out-String -Stream @htOutStringArgs | ForEach-Object { $sw.WriteLine($_) } 73 | } 74 | finally 75 | { 76 | $sw.Dispose() 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /functions/AD/Enable-AzureADPIMRole.ps1: -------------------------------------------------------------------------------- 1 | Function Enable-AzureADPIMRole 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [string]$UserPrincipalName 6 | , [String]$Role 7 | , [string]$Reason 8 | , [int]$DurationInHours 9 | ) 10 | 11 | if ($null -eq $(Get-Module -Name AzureADPreview)) 12 | { 13 | throw('Import the AzureADPreview module before using Enable-AzureADPIMRole') 14 | } 15 | 16 | try 17 | { 18 | $TenantDetail = Get-AzureADTenantDetail -ErrorAction Stop 19 | } 20 | catch 21 | { 22 | throw ('Connect to AzureAD with the Connect-AzureAD command from the AzureADPreview Module before using Enable-AzureADPIMRole') 23 | } 24 | 25 | $ProviderID = 'aadRoles' #the default keyword for this type of role 26 | $GetRDParams = @{ 27 | ProviderID = $ProviderID 28 | ResourceID = $TenantDetail.ObjectId 29 | } 30 | 31 | $RoleDefinitions = Get-AzureADMSPrivilegedRoleDefinition @GetRDParams 32 | 33 | $RoleDefinition = $RoleDefinitions.where( { $_.DisplayName -eq $Role }) 34 | if ($null -eq $RoleDefinition) 35 | { 36 | throw("The value provided for the Role parameter, $Role, is not a valid Role DisplayName.") 37 | } 38 | 39 | try 40 | { 41 | $User = Get-AzureADUser -ObjectId $UserPrincipalName -ErrorAction Stop 42 | } 43 | catch 44 | { 45 | throw("User object for $UserPrincipalName could not be retrieved.") 46 | } 47 | 48 | $start = Get-Date 49 | $end = $start.AddHours($DurationInHours) 50 | $schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule 51 | $schedule.Type = "Once" 52 | $schedule.StartDateTime = $start.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") 53 | $schedule.endDateTime = $end.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") 54 | 55 | $OpenAADPRARequestParams = @{ 56 | ProviderID = $ProviderID 57 | ResourceId = $TenantDetail.ObjectID 58 | RoleDefinitionId = $RoleDefinition.Id 59 | SubjectId = $User.ObjectId 60 | Type = 'UserAdd' 61 | AssignmentState = 'Active' 62 | Schedule = $schedule 63 | Reason = $Reason 64 | } 65 | 66 | try 67 | { 68 | $AssignmentDetail = Open-AzureADMSPrivilegedRoleAssignmentRequest @OpenAADPRARequestParams -ErrorAction Stop 69 | [PSCustomObject]@{ 70 | TenantID = $TenantDetail.ObjectId 71 | Role = $Role 72 | RoleDefinitionId = $RoleDefinition.Id 73 | User = $UserPrincipalName 74 | UserId = $User.objectId 75 | AssignmentState = $AssignmentDetail.AssignmentState 76 | Reason = $AssignmentDetail.Reason 77 | Start = $start 78 | End = $end 79 | } 80 | } 81 | catch 82 | { 83 | throw($_) 84 | } 85 | } 86 | 87 | #$PrivilegedRoleAdministrator = $RoleDefinitions.where({$_.DisplayName -eq 'Privileged Role Administrator'}) 88 | # Get all role assignments for a user 89 | 90 | #$GetURAParams = @{ 91 | # ProviderId = $ProviderID 92 | # ResourceID = $Connection.TenantID 93 | # Filter = "subjectId eq '$($User.ObjectId)'" 94 | #} 95 | #$UserRoleAssignments = Get-AzureADMSPrivilegedRoleAssignment @GetURAParams 96 | -------------------------------------------------------------------------------- /functions/MSOL/Enable-AzureADPIMRole.ps1: -------------------------------------------------------------------------------- 1 | Function Enable-AzureADPIMRole 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [string]$UserPrincipalName 6 | , [String]$Role 7 | , [string]$Reason 8 | , [int]$DurationInHours 9 | ) 10 | 11 | if ($null -eq $(Get-Module -Name AzureADPreview)) 12 | { 13 | throw('Import the AzureADPreview module before using Enable-AzureADPIMRole') 14 | } 15 | 16 | try 17 | { 18 | $TenantDetail = Get-AzureADTenantDetail -ErrorAction Stop 19 | } 20 | catch 21 | { 22 | throw ('Connect to AzureAD with the Connect-AzureAD command from the AzureADPreview Module before using Enable-AzureADPIMRole') 23 | } 24 | 25 | $ProviderID = 'aadRoles' #the default keyword for this type of role 26 | $GetRDParams = @{ 27 | ProviderID = $ProviderID 28 | ResourceID = $TenantDetail.ObjectId 29 | } 30 | 31 | $RoleDefinitions = Get-AzureADMSPrivilegedRoleDefinition @GetRDParams 32 | 33 | $RoleDefinition = $RoleDefinitions.where( { $_.DisplayName -eq $Role }) 34 | if ($null -eq $RoleDefinition) 35 | { 36 | throw("The value provided for the Role parameter, $Role, is not a valid Role DisplayName.") 37 | } 38 | 39 | try 40 | { 41 | $User = Get-AzureADUser -ObjectId $UserPrincipalName -ErrorAction Stop 42 | } 43 | catch 44 | { 45 | throw("User object for $UserPrincipalName could not be retrieved.") 46 | } 47 | 48 | $start = Get-Date 49 | $end = $start.AddHours($DurationInHours) 50 | $schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule 51 | $schedule.Type = "Once" 52 | $schedule.StartDateTime = $start.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") 53 | $schedule.endDateTime = $end.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") 54 | 55 | $OpenAADPRARequestParams = @{ 56 | ProviderID = $ProviderID 57 | ResourceId = $TenantDetail.ObjectID 58 | RoleDefinitionId = $RoleDefinition.Id 59 | SubjectId = $User.ObjectId 60 | Type = 'UserAdd' 61 | AssignmentState = 'Active' 62 | Schedule = $schedule 63 | Reason = $Reason 64 | } 65 | 66 | try 67 | { 68 | $AssignmentDetail = Open-AzureADMSPrivilegedRoleAssignmentRequest @OpenAADPRARequestParams -ErrorAction Stop 69 | [PSCustomObject]@{ 70 | TenantID = $TenantDetail.ObjectId 71 | Role = $Role 72 | RoleDefinitionId = $RoleDefinition.Id 73 | User = $UserPrincipalName 74 | UserId = $User.objectId 75 | AssignmentState = $AssignmentDetail.AssignmentState 76 | Reason = $AssignmentDetail.Reason 77 | Start = $start 78 | End = $end 79 | } 80 | } 81 | catch 82 | { 83 | throw($_) 84 | } 85 | } 86 | 87 | #$PrivilegedRoleAdministrator = $RoleDefinitions.where({$_.DisplayName -eq 'Privileged Role Administrator'}) 88 | # Get all role assignments for a user 89 | 90 | #$GetURAParams = @{ 91 | # ProviderId = $ProviderID 92 | # ResourceID = $Connection.TenantID 93 | # Filter = "subjectId eq '$($User.ObjectId)'" 94 | #} 95 | #$UserRoleAssignments = Get-AzureADMSPrivilegedRoleAssignment @GetURAParams 96 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-DirectorySynchronization.ps1: -------------------------------------------------------------------------------- 1 | Function Test-DirectorySynchronization { 2 | 3 | [cmdletbinding()] 4 | Param( 5 | [string]$identity 6 | , 7 | [int]$MaxSyncWaitMinutes = 15 8 | , 9 | #could possibly look this up on the DirSync Server task history? 10 | [int]$DeltaSyncExpectedMinutes = 2 11 | , 12 | $SyncCheckInterval = 15 13 | , 14 | $ExchangeOrganization = 'OL' 15 | , 16 | $RecipientAttributeToCheck = 'RecipientType' 17 | , 18 | $RecipientAttributeValue 19 | , 20 | [switch]$InitiateSynchronization 21 | ) 22 | Begin {} 23 | Process 24 | { 25 | Connect-Exchange -ExchangeOrganization $ExchangeOrganization 26 | $Recipient = Invoke-ExchangeCommand -cmdlet Get-Recipient -ExchangeOrganization $ExchangeOrganization -string "-Identity $Identity -ErrorAction SilentlyContinue" -ErrorAction SilentlyContinue 27 | if ($Recipient.$RecipientAttributeToCheck -eq $RecipientAttributeValue) { 28 | Write-Log -Message "Checking $identity for value $RecipientAttributeValue in attribute $RecipientAttributeToCheck." -EntryType Succeeded 29 | Write-Output -InputObject $true 30 | } 31 | elseif ($InitiateSynchronization) { 32 | Write-Log -Message "Initiating Directory Synchronization and Checking/Waiting for a maximum of $MaxSyncWaitMinutes minutes." -EntryType Notification 33 | $stopwatch = [Diagnostics.Stopwatch]::StartNew() 34 | $minutes = 0 35 | Start-DirectorySynchronization 36 | do { 37 | Start-Sleep -Seconds $SyncCheckInterval 38 | Connect-Exchange -ExchangeOrganization $ExchangeOrganization 39 | Write-Log -Message "Checking $identity for value $RecipientAttributeValue in attribute $RecipientAttributeToCheck." -EntryType Attempting 40 | $Recipient = Invoke-ExchangeCommand -cmdlet Get-Recipient -ExchangeOrganization $ExchangeOrganization -string "-Identity $Identity -ErrorAction SilentlyContinue" -ErrorAction SilentlyContinue 41 | #check if we have already waited the DeltaSyncExpectedMinutes. If so, request a new directory synchronization 42 | if (($stopwatch.Elapsed.Minutes % $DeltaSyncExpectedMinutes -eq 0) -and ($stopwatch.Elapsed.Minutes -ne $minutes)) { 43 | $minutes = $stopwatch.Elapsed.Minutes 44 | Write-Log -Message "$minutes minutes of a maximum $MaxSyncWaitMinutes minutes elapsed. Initiating additional Directory Synchronization attempt." -EntryType Notification 45 | Start-DirectorySynchronization 46 | } 47 | } 48 | until ($Recipient.$RecipientAttributeToCheck -eq $RecipientAttributeValue -or $stopwatch.Elapsed.Minutes -ge $MaxSyncWaitMinutes) 49 | $stopwatch.Stop() 50 | if ($stopwatch.Elapsed.Minutes -ge $MaxSyncWaitMinutes) { 51 | Write-Log -Message 'Maximum Synchronization Wait Time Met or Exceeded' -EntryType Notification -ErrorLog 52 | } 53 | if ($Recipient.$RecipientAttributeToCheck -eq $RecipientAttributeValue) { 54 | Write-Log -Message "Checking $identity for value $RecipientAttributeValue in attribute $RecipientAttributeToCheck." -EntryType Succeeded 55 | Write-Output -InputObject $true 56 | } 57 | else {Write-Output -InputObject $false} 58 | } 59 | else {Write-Output -InputObject $false} 60 | }#Process 61 | End {} 62 | 63 | } 64 | -------------------------------------------------------------------------------- /functions/Outlook/Send-OutlookMail.ps1: -------------------------------------------------------------------------------- 1 | Function Send-OutlookMail 2 | { 3 | [cmdletbinding()] 4 | param( 5 | [parameter()] 6 | [string]$Subject 7 | , 8 | [parameter()] 9 | $Body 10 | , 11 | [parameter()] 12 | [switch]$BodyAsHtml 13 | , 14 | [parameter()] 15 | [string[]]$Attachment 16 | , 17 | [parameter()] 18 | [string[]]$To 19 | , 20 | [parameter()] 21 | [string[]]$BCC 22 | , 23 | [parameter()] 24 | [string[]]$CC 25 | , 26 | [parameter()] 27 | [alias('SendOnBehalf')] 28 | [string]$SendAS #the Actual 'Send As' or 'SendOnBehalf' will depend on the permissions held by the sending account. 29 | , 30 | [parameter()] 31 | [validateset('Normal', 'High', 'Low')] 32 | [string]$Priority 33 | ) 34 | Begin 35 | { 36 | try 37 | { 38 | $outlook = [Runtime.Interopservices.Marshal]::GetActiveObject('Outlook.Application') 39 | $script:outlookWasAlreadyRunning = $true 40 | } 41 | catch 42 | { 43 | try 44 | { 45 | $Outlook = New-Object -ComObject Outlook.Application 46 | $script:outlookWasAlreadyRunning = $false 47 | } 48 | catch 49 | { 50 | Write-Error "You must exit Outlook first." 51 | } 52 | } 53 | } 54 | Process 55 | { 56 | $Mail = $Outlook.CreateItem(0) 57 | $To.ForEach( { 58 | $null = $Mail.Recipients.add($_) 59 | }) 60 | $CC.ForEach( { 61 | $Recip = $Mail.Recipients.add($_) 62 | $Recip.Type = 2 63 | }) 64 | $BCC.ForEach( { 65 | $Recip = $Mail.Recipients.add($_) 66 | $Recip.Type = 3 67 | }) 68 | $Mail.Subject = $Subject 69 | switch ($BodyAsHtml) 70 | { 71 | $true 72 | { 73 | $Mail.htmlBody = $Body 74 | } 75 | $false 76 | { 77 | $mail.Body = $Body 78 | } 79 | } 80 | $attachment.foreach( { 81 | $a = $Mail.Attachments.add($_) 82 | $filename = Split-Path -Path $_ -Leaf 83 | $a.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", $filename) 84 | }) 85 | 86 | if ($PSBoundParameters.ContainsKey('Priority')) 87 | { 88 | switch ($Priority) 89 | { 90 | 'Normal' 91 | { $Mail.Importance = 1 } 92 | 'High' 93 | { $Mail.Importance = 2 } 94 | 'Low' 95 | { $Mail.Importance = 0 } 96 | } 97 | } 98 | if (-not [string]::IsNullOrWhiteSpace($SendAs)) 99 | { 100 | $mail.SentOnBehalfOfName = $SendAs 101 | } 102 | 103 | $Mail.Send() 104 | } 105 | End 106 | { 107 | #if ($outlookWasAlreadyRunning -eq $false) 108 | #{ 109 | # $Outlook.Quit() 110 | #} 111 | #$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook) 112 | #$Outlook = $null 113 | } 114 | } -------------------------------------------------------------------------------- /functions/Windows/Search-FileIndex.ps1: -------------------------------------------------------------------------------- 1 | function Search-FileIndex 2 | { 3 | <# 4 | .PARAMETER Path 5 | Absoloute or relative path. Has to be in the Search Index for results to be presented. 6 | .PARAMETER Pattern 7 | File name or pattern to search for. Defaults to *.*. Aliased to Filter to ergonomically match Get-ChildItem. 8 | .PARAMETER Text 9 | Free text to search for in the files defined by the pattern. 10 | .PARAMETER Recurse 11 | Add the parameter to perform a recursive search. Default is false. 12 | .PARAMETER AsFSInfo 13 | Add the parameter to return System.IO.FileSystemInfo objects instead of String objects. 14 | .SYNOPSIS 15 | Uses the Windows Search index to search for files. 16 | .DESCRIPTION 17 | Uses the Windows Search index to search for files. 18 | SQL Syntax documented at https://msdn.microsoft.com/en-us/library/windows/desktop/bb231256(v=vs.85).aspx 19 | Based on https://blogs.msdn.microsoft.com/mediaandmicrocode/2008/07/13/microcode-windows-powershell-windows-desktop-search-problem-solving/ 20 | From: https://gist.github.com/arebee/1928da03047aee4167fabee0f501c72d 21 | .OUTPUTS 22 | By default one string per file found with full path. 23 | If the AsFSInfo switch is set, one System.IO.FileSystemInfo object per file found is returned. 24 | #> 25 | [CmdletBinding()] 26 | param ( 27 | [Parameter(ValueFromPipeline = $true)] 28 | [string]$Path, 29 | [Parameter(Mandatory = $false, ParameterSetName = "FullText")] 30 | [Parameter(Mandatory = $false)] 31 | [alias("Filter")] 32 | [string]$Pattern = "*.*", 33 | [Parameter(Mandatory = $false, ParameterSetName = "FullText")] 34 | [string]$Text = $null, 35 | [Parameter(Mandatory = $false)] 36 | [switch]$Recurse = $false, 37 | [Parameter(Mandatory = $false)] 38 | [switch]$AsFSInfo = $false 39 | ) 40 | if ($Path -eq "") 41 | { 42 | $Path = $PWD; 43 | } 44 | 45 | $path = (Resolve-Path -Path $path).Path 46 | 47 | $pattern = $pattern -replace "\*", "%" 48 | $path = $path.Replace('\', '/') 49 | 50 | if ((Test-Path -Path Variable:fsSearchCon) -eq $false) 51 | { 52 | $global:fsSearchCon = New-Object -ComObject ADODB.Connection 53 | $global:fsSearchRs = New-Object -ComObject ADODB.Recordset 54 | } 55 | 56 | $fsSearchCon.Open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';") 57 | 58 | [string]$queryString = "SELECT System.ItemPathDisplay FROM SYSTEMINDEX WHERE System.FileName LIKE '" + $pattern + "' " 59 | if ([System.String]::IsNullOrEmpty($Text) -eq $false) 60 | { 61 | $queryString += "AND FREETEXT('" + $Text + "') " 62 | } 63 | 64 | if ($Recurse) 65 | { 66 | $queryString += "AND SCOPE='file:" + $path + "' ORDER BY System.ItemPathDisplay" 67 | } 68 | else 69 | { 70 | $queryString += "AND DIRECTORY='file:" + $path + "' ORDER BY System.ItemPathDisplay" 71 | } 72 | $fsSearchRs.Open($queryString, $fsSearchCon) 73 | # return 74 | While (-Not $fsSearchRs.EOF) 75 | { 76 | if ($AsFSInfo) 77 | { 78 | # Return a FileSystemInfo object 79 | [System.IO.FileSystemInfo]$(Get-Item -LiteralPath ($fsSearchRs.Fields.Item("System.ItemPathDisplay").Value) -Force) 80 | } 81 | else 82 | { 83 | $fsSearchRs.Fields.Item("System.ItemPathDisplay").Value 84 | } 85 | $fsSearchRs.MoveNext() 86 | } 87 | $fsSearchRs.Close() 88 | $fsSearchCon.Close() 89 | } -------------------------------------------------------------------------------- /scripts/OneShellSetup.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [parameter()] 4 | [validateset('PowerShellModules', 'MyModules')] 5 | $InstallModuleIn 6 | ) 7 | function Get-UninstallEntry 8 | { 9 | [cmdletbinding(DefaultParameterSetName = 'SpecifiedProperties')] 10 | param 11 | ( 12 | [parameter(ParameterSetName = 'Raw')] 13 | [switch]$raw 14 | , 15 | [parameter(ParameterSetName = 'SpecifiedProperties')] 16 | [string[]]$property = @('DisplayName', 'DisplayVersion', 'InstallDate', 'Publisher') 17 | ) 18 | # paths: x86 and x64 registry keys are different 19 | if ([IntPtr]::Size -eq 4) 20 | { 21 | $path = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 22 | } 23 | else 24 | { 25 | $path = @( 26 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 27 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' 28 | ) 29 | } 30 | $UninstallEntries = Get-ItemProperty $path 31 | # use only with name and unistall information 32 | #.{process{ if ($_.DisplayName -and $_.UninstallString) { $_ } }} | 33 | # select more or less common subset of properties 34 | #Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, HelpLink, UninstallString | 35 | # and finally sort by name 36 | #Sort-Object DisplayName 37 | if ($raw) {$UninstallEntries | Sort-Object -Property DisplayName} 38 | else 39 | { 40 | $UninstallEntries | Sort-Object -Property DisplayName | Select-Object -Property $property 41 | } 42 | }#end function Get-UninstallEntry 43 | #region TestPrereqs 44 | $SoftwareInstalled = @(Get-UninstallEntry) 45 | 46 | $PreReqsTest = @{ 47 | PSVersion = $PSVersionTable.PSVersion.ToString() -ge 4 48 | MOSIA = 'Microsoft Online Services Sign-in Assistant' -in $SoftwareInstalled.DisplayName 49 | #https://www.microsoft.com/en-us/download/details.aspx?id=41950&WT.mc_id=rss_alldownloads_all 50 | AzureAD = 'Microsoft Azure Active Directory Module for Windows PowerShell' -in $SoftwareInstalled.DisplayName 51 | Git = Test-CommandExists -command 'git' 52 | } 53 | 54 | if ($PreReqsTest.ContainsValue($false)) 55 | { 56 | $PreReqsTest 57 | Throw "Missing a OneShell PreRequisite" 58 | } 59 | #endregion TestPrereqs 60 | if (-not (Test-Path C:\ProgramData\OneShell)) 61 | { 62 | New-Item -Path C:\ProgramData -Name 'OneShell' -ItemType Directory 63 | } 64 | if (-not (Test-Path $env:USERPROFILE\OneShell)) 65 | { 66 | New-Item -Path $env:USERPROFILE -Name 'OneShell' -ItemType Directory 67 | } 68 | $MyDocsPath = (Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders').Personal 69 | if (-not (Test-Path "$MyDocsPath\WindowsPowerShell")) 70 | { 71 | New-Item -Path $MyDocsPath -Name 'WindowsPowerShell' -ItemType Directory 72 | } 73 | if (Test-Path "$MyDocsPath\WindowsPowerShell") 74 | { 75 | if (-not (Test-Path "$MyDocsPath\WindowsPowerShell\Modules")) 76 | { 77 | New-Item -Path "$MyDocsPath\WindowsPowerShell" -Name 'Modules' -ItemType Directory 78 | } 79 | } 80 | 81 | $MyWPSPath = "$MyDocsPath\WindowsPowerShell" 82 | $MyPSModulesPath = "$MyWPSPath\Modules" 83 | $PSModulesPath = "$env:ProgramFiles\WindowsPowershell\Modules" 84 | switch ($InstallModuleIn) 85 | { 86 | 'MyModules' 87 | {Set-Location $MyPSModulesPath} 88 | 'PowerShellModules' 89 | {Set-Location $PSModulesPath} 90 | } 91 | 92 | git clone https://github.com/exactmike/OneShell.git 93 | git clone https://github.com/exactmike/AdvancedOneShell.git 94 | git clone https://github.com/exactmike/PublicFolderMigration.git 95 | git clone https://github.com/exactmike/MoveRequestManagement.git 96 | git clone https://github.com/exactmike/MigrationDatabase.git 97 | 98 | #add more for branches above? 99 | -------------------------------------------------------------------------------- /functions/Utilities/Get-FunctionInfo.ps1: -------------------------------------------------------------------------------- 1 | using namespace System.Management.Automation 2 | using namespace System.Management.Automation.Language 3 | using namespace System.Reflection 4 | 5 | function Get-FunctionInfo 6 | { 7 | <# 8 | .SYNOPSIS 9 | Get an instance of FunctionInfo. 10 | .DESCRIPTION 11 | FunctionInfo does not present a public constructor. This function calls an internal / private constructor on FunctionInfo to create a description of a function from a script block or file containing one or more functions. 12 | .PARAMETER IncludeNested 13 | By default functions nested inside other functions are ignored. Setting this parameter will allow nested functions to be discovered. 14 | .PARAMETER FilePath 15 | The path to a file containing one or more functions. 16 | .PARAMETER ScriptBlock 17 | A script block containing one or more functions. 18 | .INPUTS 19 | System.String 20 | .OUTPUTS 21 | System.Management.Automation.FunctionInfo 22 | .EXAMPLE 23 | Get-ChildItem -Filter *.psm1 | Get-FunctionInfo 24 | 25 | Get all functions declared within the *.psm1 file and construct FunctionInfo. 26 | .EXAMPLE 27 | Get-ChildItem C:\Scripts -Filter *.ps1 -Recurse | Get-FunctionInfo 28 | 29 | Get all functions declared in all ps1 files in C:\Scripts. 30 | .NOTES 31 | Change log: 32 | 10/12/2015 - Chris Dent - Improved error handling. 33 | 28/10/2015 - Chris Dent - Created. 34 | #> 35 | 36 | [CmdletBinding(DefaultParameterSetName = 'FromPath')] 37 | [OutputType([System.Management.Automation.FunctionInfo])] 38 | param ( 39 | [Parameter(Position = 1, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'FromPath')] 40 | [Alias('FullName')] 41 | [String]$FilePath, 42 | 43 | [Parameter(ParameterSetName = 'FromScriptBlock')] 44 | [ScriptBlock]$ScriptBlock, 45 | 46 | [Switch]$IncludeNested 47 | ) 48 | 49 | begin 50 | { 51 | $executionContextType = [PowerShell].Assembly.GetType('System.Management.Automation.ExecutionContext') 52 | $constructor = [FunctionInfo].GetConstructor( 53 | [BindingFlags]'NonPublic, Instance', 54 | $null, 55 | [CallingConventions]'Standard, HasThis', 56 | ([String], [ScriptBlock], $executionContextType), 57 | $null 58 | ) 59 | } 60 | 61 | process 62 | { 63 | if ($pscmdlet.ParameterSetName -eq 'FromPath') 64 | { 65 | try 66 | { 67 | $scriptBlock = [ScriptBlock]::Create((Get-Content $FilePath -Raw)) 68 | } 69 | catch 70 | { 71 | $ErrorRecord = @{ 72 | Exception = $_.Exception.InnerException 73 | ErrorId = 'InvalidScriptBlock' 74 | Category = 'OperationStopped' 75 | } 76 | Write-Error @ErrorRecord 77 | } 78 | } 79 | 80 | if ($scriptBlock) 81 | { 82 | $scriptBlock.Ast.FindAll( { 83 | param( $ast ) 84 | 85 | $ast -is [FunctionDefinitionAst] 86 | }, 87 | $IncludeNested 88 | ) | ForEach-Object { 89 | try 90 | { 91 | $internalScriptBlock = $_.Body.GetScriptBlock() 92 | } 93 | catch 94 | { 95 | Write-Debug $_.Exception.Message 96 | } 97 | if ($internalScriptBlock) 98 | { 99 | $constructor.Invoke(([String]$_.Name, $internalScriptBlock, $null)) 100 | } 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /functions/Utilities/Get-CSVExportPropertySet.ps1: -------------------------------------------------------------------------------- 1 | Function Get-CSVExportPropertySet 2 | { 3 | 4 | <# 5 | .SYNOPSIS 6 | Creates an array of property definitions to be used with Select-Object to prepare data with multi-valued attributes for export to a flat file such as csv. 7 | 8 | .DESCRIPTION 9 | From existing input arrays of scalar and multi-valued properties, creates an array of property definitions to be used with Select-Object or Format-Table. Automates the creation of the @{n=name;e={expression}} syntax for the multi-valued properties then outputs the whole list as a single array. 10 | 11 | .PARAMETER Delimiter 12 | Used to specify the custom delimiter to be used between multi-valued entries in the multi-valued attributes input array. Default is "|" if not specified. Avoid using a "," if exporting data to a csv file later in your pipeline. 13 | 14 | .PARAMETER MultiValuedAttributes 15 | An array of attributes from your source data which you expect to contain multiple values. These will be converted to @{n=[PropertyName];e={$_.$propertyname -join $Delimiter} in the output of the function. The order of the properties in the output will be Scalar attributes (in the order provided), then MultiValuedAttributes (in the order provided). 16 | 17 | .PARAMETER ScalarAttributes 18 | An array of attributes from your source data which you expect to contain scalar values. These will be passed through directly in the output of the function. The order of the properties in the output will be Scalar attributes (in the order provided), then MultiValuedAttributes (in the order provided). 19 | 20 | .PARAMETER Sort 21 | A switch parameter to indicate that you want to sort the MultiValuedAttributes with default sorting 22 | 23 | .EXAMPLE 24 | Get-CSVExportPropertySet -Delimiter ';' -MultiValuedAttributes proxyaddresses,memberof -ScalarAttributes userprincipalname,samaccountname,targetaddress,primarysmtpaddress 25 | Name Value 26 | ---- ----- 27 | n proxyaddresses 28 | e $_.proxyaddresses -join ';' 29 | n memberof 30 | e $_.memberof -join ';' 31 | userprincipalname 32 | samaccountname 33 | targetaddress 34 | primarysmtpaddress 35 | 36 | .OUTPUTS 37 | [array] 38 | 39 | #> 40 | param 41 | ( 42 | $Delimiter = '|' 43 | , 44 | [string[]]$MultiValuedAttributes 45 | , 46 | [string[]]$ScalarAttributes 47 | , 48 | [switch]$SuppressCommonADProperties 49 | , 50 | [switch]$Sort 51 | ) 52 | $ADUserPropertiesToSuppress = @('CanonicalName', 'DistinguishedName') 53 | $CustomProperties = @( 54 | foreach ($mv in $MultiValuedAttributes) 55 | { 56 | $ExpressionString = "`$(`$_." + $mv 57 | 58 | if ($true -eq $Sort) 59 | { 60 | $ExpressionString = $ExpressionString + ' | Sort-Object ) ' 61 | } 62 | else 63 | { 64 | $ExpressionString = $ExpressionString + ')' 65 | } 66 | 67 | $ExpressionString = $ExpressionString + " -join '$Delimiter'" 68 | 69 | @{ 70 | n = $mv 71 | e = [scriptblock]::Create($ExpressionString) 72 | } 73 | }#foreach 74 | ) 75 | 76 | $outputPropertySet = 77 | if ($SuppressCommonADProperties) { ($ScalarAttributes | Where-Object { $ADUserPropertiesToSuppress -notcontains $_ }) + $CustomProperties } 78 | else { $ScalarAttributes + $CustomProperties } 79 | 80 | $outputPropertySet 81 | 82 | } 83 | -------------------------------------------------------------------------------- /functions/Utilities/Get-UniqueIPsFromTextFiles.ps1: -------------------------------------------------------------------------------- 1 | Function Get-UniqueIPsFromTextFiles { 2 | 3 | # Get-UniqueIPsFromLogs 4 | # Mike Campbell, mike@exactsolutions.biz 5 | # 2011-09-12 6 | # version .1 7 | ############################################################################################ 8 | # Lists Unique IP Addresses from a set of text based log files 9 | # 10 | # Regular Expressions to match IP addresses and description below borrowed from: 11 | # 12 | # http://www.regular-expressions.info/examples.html 13 | # 14 | # Matching an IP address is another good example of a trade-off between regex complexity 15 | # and exactness. 16 | # \b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b 17 | # will match any IP address just fine, 18 | # but will also match 999.999.999.999 as if it were a valid IP address. 19 | # Whether this is a problem depends on the files or data you intend to apply the regex to. 20 | # To restrict all 4 numbers in the IP address to 0..255, you can use this complex beast: 21 | # \b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b 22 | # (everything on a single line). 23 | # The long regex stores each of the 4 numbers of the IP address into a capturing group. 24 | # You can use these groups to further process the IP number. 25 | # If you don't need access to the individual numbers, 26 | # you can shorten the regex with a quantifier to: 27 | # \b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b 28 | # Similarly, you can shorten the quick regex to 29 | # \b(?:\d{1,3}\.){3}\d{1,3}\b 30 | # 31 | # Regex.Matches Method Learned about from here: http://halr9000.com/article/526 32 | # 33 | # Auto-Help: 34 | <# 35 | PowerShell comes with great support for regular expressions but the -match operator can only find the first occurrence of a pattern. To find all occurrences, you can use the .NET RegEx type. Here is a sample:: 36 | $text = 'multiple emails like tobias.weltner@email.de and tobias@powershell.de in a string' 37 | $emailpattern = '(?i)\b([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b' 38 | 39 | $emails = ([regex]$emailpattern).Matches($text) | 40 | ForEach-Object { $_.Groups[1].Value } 41 | 42 | $emails[0] 43 | 44 | "*" * 100 45 | 46 | $emails 47 | Note the statement "(?i)" in the regular expression pattern description. The RegEx object by default works case-sensitive. To ignore case, use this control statement. 48 | #> 49 | <#.Synopsis 50 | Searches a set of log files for unique IP addresses, with optional advanced regex matching 51 | .Parameter LogFileLocation 52 | A string value specifying the path to the log files to be parsed for unique IP addresses. 53 | Default value is the current path 54 | .Parameter Advanced 55 | Uses an advanced RegEx that avoids matching non IP addresses such as 999.999.999.999 56 | .Parameter LogFileExtension 57 | Allows user to specify the log folder extension. Default is .log. 58 | #> 59 | [CmdletBinding()] 60 | param ( 61 | [parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] 62 | [string]$LogFileLocation = (get-location).path 63 | , 64 | [parameter()] 65 | [switch]$Advanced 66 | , 67 | [parameter()] 68 | [string]$LogFileExtension = '*.log' 69 | ) 70 | BEGIN {} 71 | PROCESS 72 | { 73 | # get the log file content and store the strings in an array 74 | $LogStrings = Get-ChildItem -Path $LogFileLocation -Filter $LogFileExtension | Get-Content 75 | 76 | # Determine if the user specified the Advanced RegEx and create Regex Variable Accordingly 77 | 78 | If ($Advanced) 79 | { 80 | [regex]$IPRegEx = '\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b' 81 | } 82 | 83 | Else 84 | { 85 | [regex]$IPRegEx = '\b(?:\d{1,3}\.){3}\d{1,3}\b' 86 | } 87 | 88 | # Locate Matching Values from the string array $LogStrings 89 | 90 | $IPRegEx.Matches($LogStrings) | Select-Object -Property Value -Unique | Sort-Object -Property Value 91 | 92 | } 93 | END {} 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /functions/TestFunctions/Test-TCPConnection.ps1: -------------------------------------------------------------------------------- 1 | Function Test-TCPConnection { 2 | 3 | <# 4 | .SYNOPSIS 5 | Tests a TCP Connection to each port specified for each ComputerName specified. Useful on systems that don't have Test-NetConnection. 6 | 7 | .DESCRIPTION 8 | Tests a TCP Connection to each port specified for each ComputerName specified and, 9 | if specified, can return details for each requested ComputerName and port. 10 | 11 | .INPUTS 12 | A string or array of strings or any object with a ComputerName property. 13 | 14 | .OUTPUTS 15 | Boolean, or if ReturnDetail is specified PSCustomObject 16 | 17 | .EXAMPLE 18 | $ComputerObject = [pscustomobject]@{ComputerName = 'LocalHost'} 19 | $computerObject | Test-TCPConnection -port 80,5985,25,443 20 | 21 | True 22 | True 23 | False 24 | False 25 | 26 | .EXAMPLE 27 | 'localhost','relayer.contoso.com' | Test-TCPConnection -port 25 -ReturnDetail 28 | 29 | ComputerName Port Connected 30 | ------------ ---- --------- 31 | localhost 25 False 32 | relayer.contoso.com 25 False 33 | 34 | .EXAMPLE 35 | Test-TCPConnection -ComputerName 'smtp.office365.com' -port 443,25,80,587,5985 -returnDetail 36 | 37 | ComputerName Port Connected 38 | ------------ ---- --------- 39 | smtp.office365.com 443 True 40 | smtp.office365.com 25 True 41 | smtp.office365.com 80 True 42 | smtp.office365.com 587 True 43 | smtp.office365.com 5985 False 44 | 45 | .EXAMPLE 46 | $testobject = [pscustomobject]@{computername = '10.10.101.55';port = 5985,25} 47 | $testobject | Test-TCPConnection -ReturnDetail 48 | 49 | ComputerName Port Connected 50 | ------------ ---- --------- 51 | 10.10.101.55 5985 True 52 | 10.10.101.55 25 False 53 | #> 54 | [cmdletbinding(DefaultParameterSetName = 'Boolean')] 55 | [OutputType([bool], ParameterSetName = "Boolean")] 56 | [OutputType([pscustomobject], ParameterSetName = "ReturnDetail")] 57 | param 58 | ( 59 | # Specify one or more ComputerNames, IP Addresses, or FQDNs to test. 60 | [parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 1)] 61 | [string[]]$ComputerName 62 | , 63 | # Specify one or more TCP Ports to test. 64 | [parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 2)] 65 | [int[]]$Port 66 | , 67 | # Specify the timeout in milliseconds. 500 is the default. 68 | [parameter(Position = 3)] 69 | [int]$Timeout = 500 70 | , 71 | # Include if you would like object output including ComputerName, Port, and Connected [bool] properties. 72 | [parameter(ParameterSetName = 'ReturnDetail', Position = 4)] 73 | [switch]$ReturnDetail 74 | ) 75 | process 76 | { 77 | foreach ($CN in $ComputerName) 78 | { 79 | foreach ($p in $Port) 80 | { 81 | $tcpClient = New-Object System.Net.Sockets.TCPClient 82 | try 83 | { 84 | $ErrorActionPreference = 'Stop' 85 | $null = $tcpClient.ConnectAsync($CN, $p) 86 | Start-Sleep -Milliseconds $Timeout 87 | $ErrorActionPreference = 'Continue' 88 | } 89 | catch 90 | { 91 | $ErrorActionPreference = 'Continue' 92 | Write-Verbose -message $_.tostring() 93 | } 94 | if ($ReturnDetail -eq $true) 95 | { 96 | [pscustomobject]@{ 97 | ComputerName = $CN 98 | Port = $p 99 | Connected = $tcpClient.Connected 100 | } 101 | } 102 | else 103 | { 104 | $tcpClient.Connected 105 | } 106 | $tcpClient.close() 107 | } 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /functions/UserInput/Read-InputBoxDialog.ps1: -------------------------------------------------------------------------------- 1 | Function Read-InputBoxDialog { 2 | 3 | # Show input box popup and return the value entered by the user. 4 | param( 5 | [string]$Message 6 | , 7 | [Alias('WindowTitle')] 8 | [string]$Title 9 | , 10 | [string]$DefaultText 11 | ) 12 | 13 | $Script:UserInput = $null 14 | #Region BuildWPFWindow 15 | # Add required assembly 16 | Add-Type -AssemblyName WindowsBase 17 | Add-Type -AssemblyName PresentationCore 18 | Add-Type -AssemblyName PresentationFramework 19 | # Create a Size Object 20 | $wpfSize = new-object System.Windows.Size 21 | $wpfSize.Height = [double]::PositiveInfinity 22 | $wpfSize.Width = [double]::PositiveInfinity 23 | # Create a Window 24 | $Window = New-Object Windows.Window 25 | $Window.Title = $WindowTitle 26 | $Window.MinWidth = 250 27 | $Window.SizeToContent = 'WidthAndHeight' 28 | $window.WindowStartupLocation = 'CenterScreen' 29 | # Create a grid container with 3 rows, one for the message, one for the text box, and one for the buttons 30 | $Grid = New-Object Windows.Controls.Grid 31 | $FirstRow = New-Object Windows.Controls.RowDefinition 32 | $FirstRow.Height = 'Auto' 33 | $grid.RowDefinitions.Add($FirstRow) 34 | $SecondRow = New-Object Windows.Controls.RowDefinition 35 | $SecondRow.Height = 'Auto' 36 | $grid.RowDefinitions.Add($SecondRow) 37 | $ThirdRow = New-Object Windows.Controls.RowDefinition 38 | $ThirdRow.Height = 'Auto' 39 | $grid.RowDefinitions.Add($ThirdRow) 40 | $ColumnOne = New-Object Windows.Controls.ColumnDefinition 41 | $ColumnOne.Width = 'Auto' 42 | $grid.ColumnDefinitions.Add($ColumnOne) 43 | $ColumnTwo = New-Object Windows.Controls.ColumnDefinition 44 | $ColumnTwo.Width = 'Auto' 45 | $grid.ColumnDefinitions.Add($ColumnTwo) 46 | # Create a label for the message 47 | $label = New-Object Windows.Controls.Label 48 | $label.Content = $Message 49 | $label.Margin = '5,5,5,5' 50 | $label.HorizontalAlignment = 'Left' 51 | $label.Measure($wpfSize) 52 | #add the label to Row 1 53 | $label.SetValue([Windows.Controls.Grid]::RowProperty, 0) 54 | $label.SetValue([Windows.Controls.Grid]::ColumnSpanProperty, 2) 55 | $textbox = New-Object Windows.Controls.TextBox 56 | $textbox.name = 'InputBox' 57 | $textbox.Text = $DefaultText 58 | $textbox.Margin = '10,10,10,10' 59 | $textbox.MinWidth = 200 60 | $textbox.SetValue([Windows.Controls.Grid]::RowProperty, 1) 61 | $textbox.SetValue([Windows.Controls.Grid]::ColumnSpanProperty, 2) 62 | $OKButton = New-Object Windows.Controls.Button 63 | $OKButton.Name = 'OK' 64 | $OKButton.Content = 'OK' 65 | $OKButton.ToolTip = 'OK' 66 | $OKButton.HorizontalAlignment = 'Center' 67 | $OKButton.VerticalAlignment = 'Top' 68 | $OKButton.Add_Click( { 69 | [Object]$sender = $args[0] 70 | [Windows.RoutedEventArgs]$e = $args[1] 71 | $Script:UserInput = $textbox.text 72 | $Window.DialogResult = $true 73 | $Window.Close() 74 | }) 75 | $OKButton.SetValue([Windows.Controls.Grid]::RowProperty, 2) 76 | $OKButton.SetValue([Windows.Controls.Grid]::ColumnProperty, 0) 77 | $OKButton.Margin = '5,5,5,5' 78 | $CancelButton = New-Object Windows.Controls.Button 79 | $CancelButton.Name = 'Cancel' 80 | $CancelButton.Content = 'Cancel' 81 | $CancelButton.ToolTip = 'Cancel' 82 | $CancelButton.HorizontalAlignment = 'Center' 83 | $CancelButton.VerticalAlignment = 'Top' 84 | $CancelButton.Margin = '5,5,5,5' 85 | $CancelButton.Measure($wpfSize) 86 | $CancelButton.Add_Click( { 87 | [Object]$sender = $args[0] 88 | [Windows.RoutedEventArgs]$e = $args[1] 89 | $Window.DialogResult = $false 90 | $Window.Close() 91 | }) 92 | $CancelButton.SetValue([Windows.Controls.Grid]::RowProperty, 2) 93 | $CancelButton.SetValue([Windows.Controls.Grid]::ColumnProperty, 1) 94 | $CancelButton.Height = $CancelButton.DesiredSize.Height 95 | $CancelButton.Width = $CancelButton.DesiredSize.Width + 10 96 | $OKButton.Height = $CancelButton.DesiredSize.Height 97 | $OKButton.Width = $CancelButton.DesiredSize.Width + 10 98 | $Grid.AddChild($label) 99 | $Grid.AddChild($textbox) 100 | $Grid.AddChild($OKButton) 101 | $Grid.AddChild($CancelButton) 102 | $window.Content = $Grid 103 | if ($window.ShowDialog()) 104 | { 105 | $Script:UserInput 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /Profile.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Profile' 3 | # 4 | # Generated by: Mike Campbell 5 | # 6 | # Generated on: 12/21/2019 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Profile.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.0.0.3' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'cc41c8e0-aa33-4532-aad7-b8cce74eab17' 22 | 23 | # Author of this module 24 | Author = 'Mike Campbell' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Unknown' 28 | 29 | # Copyright statement for this module 30 | Copyright = '2024' 31 | 32 | # Description of the functionality provided by this module 33 | # Description = '' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = '*' 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = '*' 76 | 77 | # Variables to export from this module 78 | VariablesToExport = '*' 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = '*' 82 | 83 | # DSC resources to export from this module 84 | # DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | # Tags = @() 99 | 100 | # A URL to the license for this module. 101 | LicenseUri = 'https://github.com/exactmike/Profile/blob/master/license' 102 | 103 | # A URL to the main website for this project. 104 | ProjectUri = 'https://github.com/exactmike/Profile' 105 | 106 | # A URL to an icon representing this module. 107 | # IconUri = '' 108 | 109 | # ReleaseNotes of this module 110 | # ReleaseNotes = '' 111 | 112 | } # End of PSData hashtable 113 | 114 | } # End of PrivateData hashtable 115 | 116 | # HelpInfo URI of this module 117 | # HelpInfoURI = '' 118 | 119 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 120 | # DefaultCommandPrefix = '' 121 | 122 | } 123 | --------------------------------------------------------------------------------