How can I parse distinguished names from Active Directory using Powershell to determine parent OUs? - powershell

I'm using Microsoft's ActiveDirectory module to retrieve and manipulate our domain users, and I need to easily determine the parent OU of the objects I'm retrieving. I've tried using -split ',' or .Split(','), but I keep running into issues with certain objects that have commas in them.

There is no public exposed DN parser method or class built in to the .Net libraries. It does exist because it has to be there for how some of the DirectoryServices classes seem to work, but I don't know how to call it from Powershell and it's not documented.
There is the fairly popular DNParser library on NuGet, which is a .Net library for parsing and manipulating distinguished names.
First, download the package file from NuGet. The package will be called "dnparser.1.3.3.nupkg" for example, but it's just a ZIP file. Extract the contents to a folder. The package is a single library, so all we need is .\dnparser.1.3.3\lib\net5.0\CPI.DirectoryServices.dll for Powershell v5 or .\dnparser.1.3.3\lib\netstandard1.1\CPI.DirectoryServices.dll for Powershell v6+. You only need that library. Nothing else in the package is strictly necessary.
# Load the library
Add-Type -Path 'C:\Path\To\dnparser.1.3.3\lib\netstandard1.1\CPI.DirectoryServices.dll'
Get-ADUser -Filter 'Enabled -eq "True"' |
Select-Object -First 10 |
ForEach-Object {
$DN = [CPI.DirectoryServices.DN]::new($_.DistinguishedName)
[PSCustomObject]#{
DistinguishedName = $DN.ToString()
ParentOU = $DN.Parent.ToString()
}
} |
Format-List *
You can also create the object with New-Object if you prefer that.
$DN = New-Object -TypeName CPI.DirectoryServices.DN -ArgumentList $_.DistinguishedName
There are other methods and properties in the class, but this is enough for what I need.
Warning: I have learned that DNParser, designed around RFC 2253, uses UTF-8 for encoding hex characters, while I think at least some instances of Active Directory use ISO-8859-1 (Western Latin). In short, you may have hex-escaped characters in Active Directory like ü which are escaped as \FC. These may translate to the UTF-8 unprintable character in DNParser � or \EF\BF\BD because they're in an invalid range in UTF-8. The UTF-8 equivalent would be \C3\BC, but that's ó in ISO-8859-1. There does not appear to be a way to force disable this behavior.

You could use this small helper function to parse out the RelativeDistinguishedName components in order:
function Parse-DistinghuishedName {
# See https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string[]]$DistinguishedName
)
begin {
function _UnescapeSpecial([string]$value) {
# replace all special characters formatted as BackSlash-TwoDigitHexCode
$match = ([regex]'(?i)\\([0-9a-f]{2})').Match($value)
while ($match.Success) {
$value = $value -replace "\\$($match.Groups[1].Value)", [char][convert]::ToUInt16($match.Groups[1].Value, 16)
$match = $match.NextMatch()
}
# finally, replace all backslash escaped characters
$value -replace '\\(.)', '$1'
}
}
process {
foreach ($dn in $DistinguishedName) {
$hash = [ordered]#{}
# split the string into separate RDN (RelativeDistinguishedName) components
$dn -split ',\s*(?<!\\,\s*)' | ForEach-Object {
$name, $value = ($_ -split '=', 2).Trim()
if (![string]::IsNullOrWhiteSpace($value)) {
$value = _UnescapeSpecial $value
switch ($name) {
'O' { $hash['Organization'] = $value }
'L' { $hash['City'] = $value }
'S' { $hash['State'] = $value }
'C' { $hash['Country'] = $value }
'ST' { $hash['StateOrProvince'] = $value }
'UID' { $hash['UserId'] = $value }
'STREET' { $hash['Street'] = $value }
# these RDN's can occur multiple times, so add as arrays
'CN' { $hash['Name'] += #($value) }
'OU' { $hash['OrganizationalUnit'] += #($value) }
'DC' { $hash['DomainComponent'] += #($value) }
}
}
}
$hash
}
}
}
Usage:
$dnHash = Parse-DistinghuishedName 'CN=R\fchmann\, Heinz ,OU=Test,OU=SubOU,DC=North America,DC=Fabrikam,DC=COM'
would result in an ordered Hashtable:
Name Value
---- -----
Name {Rühmann, Heinz}
OrganizationalUnit {Test, SubOU}
DomainComponent {North America, Fabrikam, COM}
To get the parent OU name, you just index into the .OrganizationalUnit element:
$dnHash.OrganizationalUnit[0] # --> 'Test' (top parent OU)
$dnHash.OrganizationalUnit[-1] # --> 'SubOU' (direct OU)

Related

Get-ADUser - want to write only one part of the OU into a variable

I have this:
Get-ADUser myuser |
Select #{n='OU';e={$_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)'}}
But I need to get only on part of the OU of a specific user which I have to define as a variable in the beginning.
I get this
OU=Users,OU=Munich,DC=xyzdom,DC=xyz
And I want to detect if the user is in the Munich OU or where ever.
So the output should be just $city and the input $username
I have no clue how to do this. But I suspect it should be not as hard to achieve this goal.
Maybe someone has time and passion to show me how :)
Thank you so much
Greetings
Thanks a lot for the help. (I can't use the city property.) My solution looks like this now:
Import-Module ActiveDirectory
$samaccountname = "Smith"
$ou = Get-ADUser $samaccountname | Select #{n='OU';e={$_.DistinguishedName.split(',')[-3].split("=")[-1]}} | FT -HideTableHeaders
$ou
Now, the output is just: Munich
I want to go on using this variable but maybe it's in a wrong format. when I try to use it with orchestrator I get an output like this: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
So maybe it has to be formated as string??? How can I do that?
I agree with Santiago that using the users AD attribute City would be a much better solution, but if you don't have that filled in on the users, you may try below.
A DistinguishedName can contain commas, escaped characters and even special characters converted to their HEX representation.
See here and there
Simply splitting a DN on the comma can therefore return unwanted results.
For this, I've written a small helper function some time ago you could use:
function Parse-DistinghuishedName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string[]]$DistinghuishedName
)
begin {
function _ReplaceSpecial([string]$value) {
# replace all special characters formatted as BackSlash-TwoDigitHexCode
$match = ([regex]'(?i)\\([0-9a-f]{2})').Match($value)
while ($match.Success) {
$value = $value -replace "\\$($match.Groups[1].Value)", [char][convert]::ToUInt16($match.Groups[1].Value, 16)
$match = $match.NextMatch()
}
# finally, replace all backslash escaped characters
$value -replace '\\(.)', '$1'
}
}
process {
foreach ($dn in $DistinghuishedName) {
$hash = [ordered]#{}
# split the string into separate RDN (RelativeDistinguishedName) components
$dn -split ',\s*(?<!\\,\s*)' | ForEach-Object {
$name, $value = ($_ -split '=', 2).Trim()
if (![string]::IsNullOrWhiteSpace($value)) {
$value = _ReplaceSpecial $value
switch ($name) {
'O' { $hash['Organization'] = $value }
'L' { $hash['City'] = $value }
'S' { $hash['State'] = $value }
'C' { $hash['Country'] = $value }
'ST' { $hash['StateOrProvince'] = $value }
'UID' { $hash['UserId'] = $value }
'STREET' { $hash['Street'] = $value }
# these RDN's can occur multiple times, so add as arrays
'CN' { $hash['Name'] += #($value) }
'OU' { $hash['OrganizationalUnit'] += #($value) }
'DC' { $hash['DomainComponent'] += #($value) }
}
}
}
$hash
}
}
}
It parses the DN into its RDN components and returns a Hashtable.
In your case, use it like:
(Parse-DistinghuishedName 'OU=Users,OU=Munich,DC=xyzdom,DC=xyz').OrganizationalUnit[1] # --> Munich

Fast Registry Searcher in Powershell

I'm trying to incorporate this search-registry script that I found on Github into one of my scripts.
https://github.com/KurtDeGreeff/PlayPowershell/blob/master/Search-Registry.ps1
To test it, I used one of the examples that it provided:
Search-Registry -StartKey HKLM -Pattern $ENV:USERNAME -MatchData
After executing, I was still prompted to enter a StartKey and a Pattern into the console. After providing that information once again, the command fails.
cmdlet Search-Registry.ps1 at command pipeline position 1
Supply values for the following parameters:
StartKey: HKLM
Pattern: $ENV:USERNAME -MatchData
You must specify at least one of: -MatchKey -MatchValue -MatchData
At C:\Users\Cole\Desktop\Powershell\Search-Registry.ps1:93 char:5
+ throw "You must specify at least one of: -MatchKey -MatchValue -M ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (You must specif...alue -MatchData:String) [], RuntimeException
+ FullyQualifiedErrorId : You must specify at least one of: -MatchKey -MatchValue -MatchData
Is there something that I'm doing incorrectly?
I have tested that same function on GitHub too and also came across errors with it, so I decided to do a complete rewrite myself.
The function below can use either Regular Expression matches or Wildcard comparisons using the -like operator.
To search for nameless default properties, either don't specify the Pattern or RegexPattern parameters at all, or use one of them with an empty string.
The function also has a Recurse switch, so it is up to you if you want to recursively search through all subkeys or not.
Note that although the function performs fast, searching the registry can take a long time to finish..
# If you want to run this on PowerShell < 3.0 use
# New-Object -TypeName PSObject -Property #{ ... } wherever it says [PSCustomObject]#{ ... }
# and change the -version value for 'requires' to 2
#requires -version 3
function Search-Registry {
<#
.SYNOPSIS
Searches the registry on one or more computers for a specified text pattern.
.DESCRIPTION
Searches the registry on one or more computers for a specified text pattern.
Supports searching for any combination of key names, value names, and/or value data.
The search pattern is either a regular expression or a wildcard pattern using the 'like' operator.
(both are case-insensitive)
.PARAMETER ComputerName
(Required) Searches the registry on the specified computer(s). This parameter supports piped input.
.PARAMETER Pattern
(Optional) Searches using a wildcard pattern and the -like operator.
Mutually exclusive with parameter 'RegexPattern'
.PARAMETER RegexPattern
(Optional) Searches using a regular expression pattern.
Mutually exclusive with parameter 'Pattern'
.PARAMETER Hive
(Optional) The registry hive rootname.
Can be any of 'HKEY_CLASSES_ROOT','HKEY_CURRENT_CONFIG','HKEY_CURRENT_USER','HKEY_DYN_DATA','HKEY_LOCAL_MACHINE',
'HKEY_PERFORMANCE_DATA','HKEY_USERS','HKCR','HKCC','HKCU','HKDD','HKLM','HKPD','HKU'
If not specified, the hive must be part of the 'KeyPath' parameter.
.PARAMETER KeyPath
(Optional) Starts the search at the specified registry key. The key name contains only the subkey.
This parameter can be prefixed with the hive name.
In that case, parameter 'Hive' is ignored as it is then taken from the given path.
Examples:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall
HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall
Software\Microsoft\Windows\CurrentVersion\Uninstall
.PARAMETER MaximumResults
(Optional) Specifies the maximum number of results per computer searched.
A value <= 0 means will return the maximum number of possible matches (2147483647).
.PARAMETER SearchKeyName
(Optional) Searches for registry key names. You must specify at least one of -SearchKeyName, -SearchPropertyName, or -SearchPropertyValue.
.PARAMETER SearchPropertyName
(Optional) Searches for registry value names. You must specify at least one of -SearchKeyName, -SearchPropertyName, or -SearchPropertyValue.
.PARAMETER SearchPropertyValue
(Optional) Searches for registry value data. You must specify at least one of -SearchKeyName, -SearchPropertyName, or -SearchPropertyValue.
.PARAMETER Recurse
(Optional) If set, the function will recurse the search through all subkeys found.
.OUTPUTS
PSCustomObjects with the following properties:
ComputerName The computer name where the search was executed
Hive The hive name used in Win32 format ("CurrentUser", "LocalMachine" etc)
HiveName The hive name used ("HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE" etc.)
HiveShortName The abbreviated hive name used ("HKCU", "HKLM" etc.)
Path The full registry path ("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall")
SubKey The subkey without the hive ("Software\Microsoft\Windows\CurrentVersion\Uninstall")
ItemType Informational: describes the type 'RegistryKey' or 'RegistryProperty'
DataType The .REG formatted datatype ("REG_SZ", "REG_EXPAND_SZ", "REG_DWORD" etc.). $null for ItemType 'RegistryKey'
ValueKind The Win32 datatype ("String", "ExpandString", "DWord" etc.). $null for ItemType 'RegistryKey'
PropertyName The name of the property. $null for ItemType 'RegistryKey'
PropertyValue The value of the registry property. $null for ItemType 'RegistryKey'
PropertyValueRaw The raw, unexpanded value of the registry property. $null for ItemType 'RegistryKey'
The difference between 'PropertyValue' and 'PropertyValueRaw' is that in 'PropertyValue' Environment names are expanded
('%SystemRoot%' in the data gets expanded to 'C:\Windows'), whereas in 'PropertyValueRaw' the data is returned as-is.
(Environment names return as '%SystemRoot%')
.EXAMPLE
Search-Registry -Hive HKLM -KeyPath SOFTWARE -Pattern $env:USERNAME -SearchPropertyValue -Recurse -Verbose
Searches HKEY_LOCAL_MACHINE on the local computer for registry values whose data contains the current user's name.
Searches like this can take a long time and you may see warning messages on registry keys you are not allowed to enter.
.EXAMPLE
Search-Registry -KeyPath 'HKEY_CURRENT_USER\Printers\Settings' -Pattern * -SearchPropertyName | Export-Csv -Path 'D:\printers.csv' -NoTypeInformation
or
Search-Registry -Hive HKEY_CURRENT_USER -KeyPath 'Printers\Settings' -Pattern * -SearchPropertyName | Export-Csv -Path 'D:\printers.csv' -NoTypeInformation
Searches HKEY_CURRENT_USER (HKCU) on the local computer for printer names and outputs it as a CSV file.
.EXAMPLE
Search-Registry -KeyPath 'HKLM:\SOFTWARE\Classes\Installer' -Pattern LastUsedSource -SearchPropertyName -Recurse
or
Search-Registry -Hive HKLM -KeyPath 'SOFTWARE\Classes\Installer' -Pattern LastUsedSource -SearchPropertyName -Recurse
Outputs the LastUsedSource registry entries on the current computer.
.EXAMPLE
Search-Registry -KeyPath 'HKCR\.odt' -RegexPattern '.*' -SearchKeyName -MaximumResults 10 -Verbose
or
Search-Registry -Hive HKCR -KeyPath '.odt' -RegexPattern '.*' -SearchKeyName -MaximumResults 10 -Verbose
Outputs at most ten matches if the specified key exists.
This command returns a result if the current computer has a program registered to open files with the .odt extension.
The pattern '.*' means match everything.
.EXAMPLE
Get-Content Computers.txt | Search-Registry -KeyPath "HKLM:\SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine" -Pattern '*' -SearchPropertyName | Export-Csv -Path 'D:\powershell.csv' -NoTypeInformation
Searches for any property name in the registry on each computer listed in the file Computers.txt starting at the specified subkey.
Output is sent to the specified CSV file.
.EXAMPLE
Search-Registry -KeyPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace' -SearchPropertyName -Recurse -Verbose
Searches for the default (nameless) properties in the specified registry key.
#>
[CmdletBinding(DefaultParameterSetName = 'ByWildCard')]
Param(
[Parameter(ValueFromPipeline = $true, Mandatory = $false, Position = 0)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory = $false, ParameterSetName = 'ByRegex')]
[string]$RegexPattern,
[Parameter(Mandatory = $false, ParameterSetName = 'ByWildCard')]
[string]$Pattern,
[Parameter(Mandatory = $false)]
[ValidateSet('HKEY_CLASSES_ROOT','HKEY_CURRENT_CONFIG','HKEY_CURRENT_USER','HKEY_DYN_DATA','HKEY_LOCAL_MACHINE',
'HKEY_PERFORMANCE_DATA','HKEY_USERS','HKCR','HKCC','HKCU','HKDD','HKLM','HKPD','HKU')]
[string]$Hive,
[string]$KeyPath,
[int32] $MaximumResults = [int32]::MaxValue,
[switch]$SearchKeyName,
[switch]$SearchPropertyName,
[switch]$SearchPropertyValue,
[switch]$Recurse
)
Begin {
# detect if the function is called using the pipeline or not
# see: https://communary.net/2015/01/12/quick-tip-determine-if-input-comes-from-the-pipeline-or-not/
# and: https://www.petri.com/unraveling-mystery-myinvocation
[bool]$isPipeLine = $MyInvocation.ExpectingInput
# sanitize given parameters
if ([string]::IsNullOrWhiteSpace($ComputerName) -or $ComputerName -eq '.') { $ComputerName = $env:COMPUTERNAME }
# parse the give KeyPath
if ($KeyPath -match '^(HK(?:CR|CU|LM|U|PD|CC|DD)|HKEY_[A-Z_]+)[:\\]?') {
$Hive = $matches[1]
# remove HKLM, HKEY_CURRENT_USER etc. from the path
$KeyPath = $KeyPath.Split("\", 2)[1]
}
switch($Hive) {
{ #('HKCC', 'HKEY_CURRENT_CONFIG') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::CurrentConfig; break }
{ #('HKCR', 'HKEY_CLASSES_ROOT') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::ClassesRoot; break }
{ #('HKCU', 'HKEY_CURRENT_USER') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::CurrentUser; break }
{ #('HKDD', 'HKEY_DYN_DATA') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::DynData; break }
{ #('HKLM', 'HKEY_LOCAL_MACHINE') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::LocalMachine; break }
{ #('HKPD', 'HKEY_PERFORMANCE_DATA') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::PerformanceData; break }
{ #('HKU', 'HKEY_USERS') -contains $_ } { $objHive = [Microsoft.Win32.RegistryHive]::Users; break }
}
# critical: Hive could not be determined
if (!$objHive) {
Throw "Parameter 'Hive' not specified or could not be parsed from the 'KeyPath' parameter."
}
# critical: no search criteria given
if (-not ($SearchKeyName -or $SearchPropertyName -or $SearchPropertyValue)) {
Throw "You must specify at least one of these parameters: 'SearchKeyName', 'SearchPropertyName' or 'SearchPropertyValue'"
}
# no patterns given will only work for SearchPropertyName and SearchPropertyValue
if ([string]::IsNullOrEmpty($RegexPattern) -and [string]::IsNullOrEmpty($Pattern)) {
if ($SearchKeyName) {
Write-Warning "Both parameters 'RegexPattern' and 'Pattern' are emtpy strings. Searching for KeyNames will not yield results."
}
}
# create two variables for output purposes
switch ($objHive.ToString()) {
'CurrentConfig' { $hiveShort = 'HKCC'; $hiveName = 'HKEY_CURRENT_CONFIG' }
'ClassesRoot' { $hiveShort = 'HKCR'; $hiveName = 'HKEY_CLASSES_ROOT' }
'CurrentUser' { $hiveShort = 'HKCU'; $hiveName = 'HKEY_CURRENT_USER' }
'DynData' { $hiveShort = 'HKDD'; $hiveName = 'HKEY_DYN_DATA' }
'LocalMachine' { $hiveShort = 'HKLM'; $hiveName = 'HKEY_LOCAL_MACHINE' }
'PerformanceData' { $hiveShort = 'HKPD'; $hiveName = 'HKEY_PERFORMANCE_DATA' }
'Users' { $hiveShort = 'HKU' ; $hiveName = 'HKEY_USERS' }
}
if ($MaximumResults -le 0) { $MaximumResults = [int32]::MaxValue }
$script:resultCount = 0
[bool]$useRegEx = ($PSCmdlet.ParameterSetName -eq 'ByRegex')
# -------------------------------------------------------------------------------------
# Nested helper function to (recursively) search the registry
# -------------------------------------------------------------------------------------
function _RegSearch([Microsoft.Win32.RegistryKey]$objRootKey, [string]$regPath, [string]$computer) {
try {
if ([string]::IsNullOrWhiteSpace($regPath)) {
$objSubKey = $objRootKey
}
else {
$regPath = $regPath.TrimStart("\")
$objSubKey = $objRootKey.OpenSubKey($regPath, $false) # $false --> ReadOnly
}
}
catch {
Write-Warning ("Error opening $($objRootKey.Name)\$regPath" + "`r`n " + $_.Exception.Message)
return
}
$subKeys = $objSubKey.GetSubKeyNames()
# Search for Keyname
if ($SearchKeyName) {
foreach ($keyName in $subKeys) {
if ($script:resultCount -lt $MaximumResults) {
if ($useRegEx) { $isMatch = ($keyName -match $RegexPattern) }
else { $isMatch = ($keyName -like $Pattern) }
if ($isMatch) {
# for PowerShell < 3.0 use: New-Object -TypeName PSObject -Property #{ ... }
[PSCustomObject]#{
'ComputerName' = $computer
'Hive' = $objHive.ToString()
'HiveName' = $hiveName
'HiveShortName' = $hiveShort
'Path' = $objSubKey.Name
'SubKey' = "$regPath\$keyName".TrimStart("\")
'ItemType' = 'RegistryKey'
'DataType' = $null
'ValueKind' = $null
'PropertyName' = $null
'PropertyValue' = $null
'PropertyValueRaw' = $null
}
$script:resultCount++
}
}
}
}
# search for PropertyName and/or PropertyValue
if ($SearchPropertyName -or $SearchPropertyValue) {
foreach ($name in $objSubKey.GetValueNames()) {
if ($script:resultCount -lt $MaximumResults) {
$data = $objSubKey.GetValue($name)
$raw = $objSubKey.GetValue($name, '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($SearchPropertyName) {
if ($useRegEx) { $isMatch = ($name -match $RegexPattern) }
else { $isMatch = ($name -like $Pattern) }
}
else {
if ($useRegEx) { $isMatch = ($data -match $RegexPattern -or $raw -match $RegexPattern) }
else { $isMatch = ($data -like $Pattern -or $raw -like $Pattern) }
}
if ($isMatch) {
$kind = $objSubKey.GetValueKind($name).ToString()
switch ($kind) {
'Binary' { $dataType = 'REG_BINARY'; break }
'DWord' { $dataType = 'REG_DWORD'; break }
'ExpandString' { $dataType = 'REG_EXPAND_SZ'; break }
'MultiString' { $dataType = 'REG_MULTI_SZ'; break }
'QWord' { $dataType = 'REG_QWORD'; break }
'String' { $dataType = 'REG_SZ'; break }
default { $dataType = 'REG_NONE'; break }
}
# for PowerShell < 3.0 use: New-Object -TypeName PSObject -Property #{ ... }
[PSCustomObject]#{
'ComputerName' = $computer
'Hive' = $objHive.ToString()
'HiveName' = $hiveName
'HiveShortName' = $hiveShort
'Path' = $objSubKey.Name
'SubKey' = $regPath.TrimStart("\")
'ItemType' = 'RegistryProperty'
'DataType' = $dataType
'ValueKind' = $kind
'PropertyName' = if ([string]::IsNullOrEmpty($name)) { '(Default)' } else { $name }
'PropertyValue' = $data
'PropertyValueRaw' = $raw
}
$script:resultCount++
}
}
}
}
# recurse through all subkeys
if ($Recurse) {
foreach ($keyName in $subKeys) {
if ($script:resultCount -lt $MaximumResults) {
$newPath = "$regPath\$keyName"
_RegSearch $objRootKey $newPath $computer
}
}
}
# close opened subkey
if (($objSubKey) -and $objSubKey.Name -ne $objRootKey.Name) { $objSubKey.Close() }
}
}
Process{
if ($isPipeLine) { $ComputerName = #($_) }
$ComputerName | ForEach-Object {
Write-Verbose "Searching the registry on computer '$ComputerName'.."
try {
$rootKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($objHive, $_)
_RegSearch $rootKey $KeyPath $_
}
catch {
Write-Error "$($_.Exception.Message)"
}
finally {
if ($rootKey) { $rootKey.Close() }
}
}
Write-Verbose "All Done searching the registry. Found $($script:resultCount) results."
}
}
It returns a collection of objects with the following properties:
ComputerName : MYMACHINE
Hive : LocalMachine
HiveName : HKEY_LOCAL_MACHINE
HiveShortName : HKLM
Path : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{F3F5824C-AD58-4728-AF59-A1EBE3392799}
SubKey : SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{F3F5824C-AD58-4728-AF59-A1EBE3392799}
ItemType : RegistryProperty
DataType : REG_SZ
ValueKind : String
PropertyName : (Default)
PropertyValue : Sticky Notes Namespace Extension for Windows Desktop Search
PropertyValueRaw : Sticky Notes Namespace Extension for Windows Desktop Search
The difference between PropertyValue and PropertyValueRaw is that in PropertyValue Environment names are expanded
('%SystemRoot%' in the data gets expanded to 'C:\Windows'), whereas in PropertyValueRaw the data is returned as-is.
(Environment names return as '%SystemRoot%')
I suspect it may have to do with the way you downloaded the file, and how you're calling it. Please try the following steps and see if that works for you.
Click the link to view the file in RAW format, or just click here
Right-click on the page and Save As... a ps1 file in your scripts folder. Note: Make sure you save it with the .ps1 extension and not as txt.
Open Powershell, browse to the directory, and call it as so...
.\Search-Registry.ps1 -StartKey HKCU -Pattern "Visual Studio" -MatchData
You can modify however you like, of course, I chose to look for Visual Studio in HKCU since I knew it would be a quick search.

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone

PowerShell - Password Generator - How to always include number in string?

I have the following PowerShell script that creates a random string of 15 digits, for use as an Active Directory password.
The trouble is, this works great most of the time, but on some occasions it doesn't use a number or symbol. I just get 15 letters. This is then not usable as an Active Directory password, as it must have at least one number or symbol in it.
$punc = 46..46
$digits = 48..57
$letters = 65..90 + 97..122
$YouShallNotPass = get-random -count 15 `
-input ($punc + $digits + $letters) |
% -begin { $aa = $null } `
-process {$aa += [char]$_} `
-end {$aa}
Write-Host "Password is $YouShallNotPass"
How would I amend the script to always have at least one random number or symbol in it?
Thank you.
You could invoke the Get-Random cmdlet three times, each time with a different input parameter (punc, digit and letters), concat the result strings and shuffle them using another Get-Random invoke:
(Get-Random -Count 15 -InputObject ([char[]]$yourPassword)) -join ''
However, why do you want to reinvent the wheel? Consider using the following GeneratePassword function:
[Reflection.Assembly]::LoadWithPartialName("System.Web")
[System.Web.Security.Membership]::GeneratePassword(15,2)
And to ensure, it contains at least one random number (you already specify the number of symbols):
do {
$pwd = [System.Web.Security.Membership]::GeneratePassword(15,2)
} until ($pwd -match '\d')
As suggested by jisaak, there is no 100% guaranty that the Membership.GeneratePassword Method generates a password that meets the AD complexity requirements.
That's why I reinvented the wheel:
Function Create-String([Int]$Size = 8, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = #(); $TokenSet = #()
If (!$TokenSets) {$Global:TokenSets = #{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?#[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"
Usage:
The Size parameter defines the length of the password.
The CharSets parameter defines the complexity where the character U,
L, N and S stands for Uppercase, Lowercase, Numerals and Symbols.
If supplied in lowercase (u, l, n or s) the returned string
might contain any of character in the concerned character set, If
supplied in uppercase (U, L, N or S) the returned string will
contain at least one of the characters in the concerned character
set.
The Exclude parameter lets you exclude specific characters that might e.g.
lead to confusion like an alphanumeric O and a numeric 0 (zero).
Examples:
To create a password with a length of 8 characters that might contain any uppercase characters, lowercase characters and numbers:
Create-Password 8 uln
To create a password with a length of 12 characters that that contains at least one uppercase character, one lowercase character, one number and one symbol and does not contain the characters OLIoli01:
Create-Password 12 ULNS "OLIoli01"
For the latest New-Password version: use:
Install-Script -Name PowerSnippets.New-Password
Command to Generate Random passwords by using existing funciton:
[system.web.security.membership]::GeneratePassword(x,y)
x = Length of the password
y = Complexity
General Error:
Unable to find type [system.web.security.membership]. Make sure that the assembly that contains this type is loaded.
Solution:
Run the below command:
Add-Type -AssemblyName System.web;
Another solution:
function New-Password() {
param(
[int] $Length = 10,
[bool] $Upper = $true,
[bool] $Lower = $true,
[bool] $Numeric = $true,
[string] $Special
)
$upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
$lowerChars = "abcdefghijklmnopqrstuvwxyz"
$numericChars = "0123456789"
$all = ""
if ($Upper) { $all = "$all$upperChars" }
if ($Lower) { $all = "$all$lowerChars" }
if ($Numeric) { $all = "$all$numericChars" }
if ($Special -and ($special.Length -gt 0)) { $all = "$all$Special" }
$password = ""
for ($i = 0; $i -lt $Length; $i++) {
Write-Host "password: [$password]"
$password = $password + $all[$(Get-Random -Minimum 0 -Maximum $all.Length)]
}
$valid = $true
if ($Upper -and ($password.IndexOfAny($upperChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Lower -and ($password.IndexOfAny($lowerChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Numeric -and ($password.IndexOfAny($numericChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Special -and $Special.Length -gt 1 -and ($password.IndexOfAny($Special.ToCharArray()) -eq -1)) { $valid = $false }
if (-not $valid) {
$password = New-Password `
-Length $Length `
-Upper $Upper `
-Lower $Lower `
-Numeric $Numeric `
-Special $Special
}
return $password
}
Flexible enough to set length, turn on/of upper, lower, and numeric, and set the list of specials.
My take on generating passwords in PowerShell, based on what I've found here and in the Internets:
#Requires -Version 4.0
[CmdletBinding(PositionalBinding=$false)]
param (
[Parameter(
Mandatory = $false,
HelpMessage = "Minimum password length"
)]
[ValidateRange(1,[int]::MaxValue)]
[int]$MinimumLength = 24,
[Parameter(
Mandatory = $false,
HelpMessage = "Maximum password length"
)]
[ValidateRange(1,[int]::MaxValue)]
[int]$MaximumLength = 42,
[Parameter(
Mandatory = $false,
HelpMessage = "Characters which can be used in the password"
)]
[ValidateNotNullOrEmpty()]
[string]$Characters = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM##%*-_+:,.'
)
(1..(Get-Random -Minimum $MinimumLength -Maximum $MaximumLength) `
| %{ `
$Characters.GetEnumerator() | Get-Random `
}) -join ''
I preferred this over using System.Web, not to introduce dependencies, which could change with .Net / .Net Core versions.
My variation also allows random password length (in specified range), is fairly concise (apart from the parameters section, which is quite verbose, to enforce some validations and provide defaults) and allows character repetitions (as opposite to the code in the question, which never repeats the same character).
I understand, that this does not guarantee a digit in the password. This however can be addressed in different ways. E.g. as was suggested, to repeat the generation until the password matches the requirements (contains a digit). My take would be:
Generate a random password.
If it does not contain a digit (or always):
Use a random function to get 1 random digit.
Add it to the random password.
Randomize the order of the result (so the digit is not necessarily always at the end).
Assuming, that the above script would be named "Get-RandomPassword.ps1", it could look like this:
$pass = .\Get-RandomPassword.ps1
$pass += (0..9 | Get-Random)
$pass = (($pass.GetEnumerator() | Get-Random -Count $pass.Length) -join '')
Write-Output $pass
This can be generalized, to enforce using any character category:
$sets = #('abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '()-_=+[{]};:''",<.>/?`~')
$pass = .\Get-RandomPassword.ps1 -Characters ($sets -join '')
foreach ($set in $sets) {
$pass += ($set.GetEnumerator() | Get-Random)
}
$pass = (($pass.GetEnumerator() | Get-Random -Count $pass.Length) -join '')
Write-Output $pass
I wrote a secure password generator function in PowerShell, maybe this will be useful to someone.
Similar to the accepted answer, this script also uses Get-Random (twice), and also regular expression matching to ensure the output is secure.
The difference in this script is that the password length can also be randomised.
(To hard set a password length, just set the MinimumPasswordLength and MaximumPasswordLength values to the the same length.)
It also allows an easy to edit character set, and also has a regex to ensure a decent password has been generated with all of the following characteristics:
(?=.*\d) must contain at least one numerical character
(?=.*[a-z]) must contain at least one lowercase character
(?=.*[A-Z]) must contain at least one uppercase character
(?=.*\W) must contain at least one non-word character
The answer to your question about always including a number in your generated output can be solved by checking the output with a regex match (just use the parts of the regex that you need, based on the explanations above), the example here checks for uppercase, lowercase, and numerical:
$Regex = "(?=.*\d)(?=.*[a-z])(?=.*[A-Z])"
do {
$Password = ([string]($AllowedPasswordCharacters |
Get-Random -Count $PasswordLength) -replace ' ')
} until ($Password -cmatch $Regex)
$Password
Here is the full script:
Function GeneratePassword
{
cls
$MinimumPasswordLength = 12
$MaximumPasswordLength = 16
$PasswordLength = Get-Random -InputObject ($MinimumPasswordLength..$MaximumPasswordLength)
$AllowedPasswordCharacters = [char[]]'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?##£$%^&'
$Regex = "(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)"
do {
$Password = ([string]($AllowedPasswordCharacters |
Get-Random -Count $PasswordLength) -replace ' ')
} until ($Password -cmatch $Regex)
$Password
}
GeneratePassword
I had the same issue here is the snippet I used to create my alphanumerical password its simple all I have done is used ASCII regex replace to make it nice.
Function Password-Generator ([int]$Length)
{
# Generate passwords just call password-generator(lenght of password)
$Assembly = Add-Type -AssemblyName System.Web
$RandomComplexPassword = [System.Web.Security.Membership]::GeneratePassword($Length,2)
$AlphaNumericalPassword = $RandomComplexPassword -replace '[^\x30-\x39\x41-\x5A\x61-\x7A]+'
Write-Output $AlphaNumericalPassword
}
I've created this. You can choose how many Pwd to create
$howoften = Read-Host "How many would you like to create: "
$i = 0
do{
(-join(1..42 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
$i++
} until ($i -match $howoften)
To change the length of the pwd simply edit the "42" in line 4
(-join(1..**42** | ForEach ...

How can I pass dynamic parameters to powershell script and iterate over the list?

I want to create a powershell script that accepts dynamic parameters and I also want to iterate through them.
eg:
I call the powershell script in the following manner.
ParametersTest.ps1 -param1 value1 -param2 value2 -param3 value3
And I should be able to access my params inside the script as follows:
for($key in DynamicParams) {
$paramValue = DynamicParams[$key];
}
Is there anyway to do this in powershell? Thanks in advance.
There is nothing built-in like that (essentially you're asking for PowerShell parameter parsing in the absence of any definition of those parameters). You can emulate it, though. With $args you can get at all arguments of the function as an array. You can then iterate that and decompose it into names and values:
$DynamicParams = #{}
switch -Regex ($args) {
'^-' {
# Parameter name
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
$name = $_ -replace '^-'
}
'^[^-]' {
# Value
$value = $_
}
}
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
To iterate over dynamic parameters you can either do something like you wrote
foreach ($key in $DynamicParams.Keys) {
$value = $DynamicParams[$key]
}
(note the foreach, not for, the latter of which cannot work like you wrote it) or just iterate normally over the hash table:
$DynamicParams.GetEnumerator() | ForEach-Object {
$name = $_.Key
$value = $_.Value
}