Powershell trim text using a special character - powershell

I have a list of hostnames which have the domain and subdomian in the FQDN. I need to get only the parent (root) domain name from it.
I have tried with substring but have not succeeded.
abc.xyz.me.com
def.me.com
ghi.sub.new.com
jkl.sup.old.com
these are some examples and from the list and I would like to get the root domains (me,new,old).

The simple way would be to use split and get the index of -2
$FQDN = ('abc.xyz.me.com', 'def.me.com', 'ghi.sub.new.com', 'jkl.sup.old.com')
ForEach ($Domain in $FQDN)
{
write-host ($Domain.split(".")[-2])
}
You probably want to do more than just write to host, but that should give you the idea.

Split the string, reverse the array and then pull the second member
$list = #(
"abc.xyz.me.com",
"def.me.com",
"ghi.sub.new.com",
"jkl.sup.old.com"
)
$List | ForEach-Object {
[Array]::Reverse(($Arr = $_.Split(".")))
$TLD, $SLD, $Null = $Arr
$TLD # You're top level domain
$SLD # The second level domain
# The rest of the values after those two get sent to null land
}

Here's a solution that gets a unique list of the full parent domains into a variable named $ParentDomains:
$Domains = 'abc.xyz.me.com', 'def.me.com', 'ghi.sub.new.com', 'jkl.sup.old.com'
$ParentDomains = $Domains | ForEach-Object {
$Domain = $_.Split('.')
$Domain[-2]+'.'+$Domain[-1]
} | Get-Unique
$ParentDomains
Explanation:
Iterates through the list of domains via ForEach-Object. Each domain in the loop is represented by $_.
Splits the string on the '.' character
Uses the index indicator [] to get the second to last [-2] and last [-1] items in each array and outputs them as a new string separated by a '.'.
Pipes the result to Get-Unique to remove duplicates.

Related

Powershell - How to use an * as a String Filter

I need to filter out only the AD groups from the Net User command in powershell. All the AD groups begin with an * so i would like to filter out the string by displaying everything that's preceeded by an *
I get an error since '*' is a special character and cannot be used. How do i get powershell to ignore it as a special character?
I cannot use any other commands to get AD groups, so Get-AD is not an option. i only have Net user to work with.
My base script,
Net User USER /domain | Select-String '*'
I cannot use any other script than Net user to accomplish this task, even though Get-AD would be simpler, i do not have the option.
Santiago's helpful answer shows a more robust, OO solution that is much more in the spirit of PowerShell.
To answer your question as asked:
Select-String by default interprets its (positionally implied) -Pattern argument as a regex (regular expression), where * is a metacharacter.
While \-escaping regex metacharacters is possible (and is necessary in the event that you need more sophisticated matching that requires a regex), the direct solution is to add the -SimpleMatch switch, which causes the -Pattern argument to be interpreted as a literal (verbatim) string:
net user $someuser /domain | Select-String * -SimpleMatch
Also note that what Select-String outputs by default aren't just the matching input lines as-is, but Microsoft.PowerShell.Commands.MatchInfo objects that provide metadata for each match, with the matching line text stored in the .Line property.
While that distinction doesn't matter much for displaying results, it may for programmatic processing, so if you only want to output the text of the matching lines, add -Raw in PowerShell (Core) 7+, or pipe to | ForEach-Object Line in Windows PowerShell.
The above will show those net user output lines that contain a literal *, and therefore all group memberships, which is good enough for the human observer.
You indeed need regex matching and operations if you want to extract the group names individually, for later programmatic processing:
# Use an ordered hashtable to collect the group names in,
# with keys 'Local' and 'Global', targeting the *current* user in this example.
$groupMemberships = [ordered] #{}
(((net user $env:USERNAME) -join "`n") -split "`n`n")[-1] -split '\n' -match ' \*' |
ForEach-Object {
$tokens = $_ -split ' \*'
if ($tokens[0] -notmatch '^ ') {
$key = if ($groupMemberships.Count -eq 0) { 'Local' } else { 'Global' }
}
$groupMemberships[$key] += #($tokens[1..($tokens.Count-1)].Trim())
}
$groupMemberships # display result.
Sample output:
Name Value
---- -----
Local { Administrators }
Global { Department1, Region1 }
That is $groupMemberships.Local $groupMemberships.Global then contains the name(s) of the local / global (AD) groups the user is a member of, respectively, as an array.
Note:
The solution above is complex, because it tries to be as robust as possible.
Notably, it is possible - albeit not likely in practice - that output lines that are unrelated to group names contain * as well, notably the Comment and User's comment fields.
Therefore, only the last paragraph of net user's output is considered, which is known to contain the group names - note that matching lines by field-name parts such as Local and Global is explicitly avoided, as the field names are localized based on your system's display language.
The last paragraph is known to list the local group memberships first, followed by the global (AD) ones. Each line in the last paragraph can contain multiple (*-prefixed) group names and there can be overflow lines for additional groups that don't fit on the first line for the given scope. Such overflow flow lines can be detected by starting with whitespace.
Instead of trying to parse the output from net user USER /domain I would use what's already available in powershell. You can get the current logged on user's Active Directory Group Membership using adsi and adsisearcher.
Here are 2 different ways of accomplishing it.
By querying the user's memberof attribute:
$searcher = [adsisearcher]::new(
[adsi] "LDAP://$env:USERDNSDOMAIN",
[string] "(cn=$env:USERNAME)",
[string[]] ("memberOf", "cn")
)
$searcher.FindOne().Properties['memberof'] | ForEach-Object {
$searcher.Filter = "(distinguishedName=$_)"
$searcher.FindOne().Properties['cn'][0]
}
By querying all groups having the user as a member:
$searcher = [adsisearcher]::new(
[adsi] "LDAP://$env:USERDNSDOMAIN",
[string] "(cn=$env:USERNAME)",
[string[]] ("distinguishedName", "cn")
)
$userDn = $searcher.FindOne().Properties['distinguishedName'][0]
$searcher.Filter = "(&(objectCategory=group)(member=$userDn))"
$searcher.FindAll() | ForEach-Object {
$_.Properties['cn'][0]
}
You can use a backslash to escape regex special characters and use ^ to specify start of string:
> #("a", "*b", "c*", "*d", "e**") | Select-String -Pattern '^\*'
*b
*d
So, to display the groups you could use, for example:
Net User USER /domain | % { $_ -split "\s+" -match "^\*" }
As per the comment, if the group names may contain spaces then obviously splitting on space characters would be inappropiate.
An alternative:
Net User USER /domain | % { $_ -split '^[^*]+\*?' -match '.+' }
Or, if we only want to look at the lines beginning "Local Group Memberships" or "Global Group Memberships" we could use, for example:
Net User USER /domain |
? { $_ -match '^(?:Local|Global) Group Memberships +\*(.+)' } | % { $matches[1] }

Get CN value from ADUser DistinguishedName

I have a PS script that checks some custom user's properties in Active Directory.
One of the properties is "Manager".
$data = Get-ADUser $user -Properties * | Select-Object DisplayName, LockedOut, Enabled, LastLogonDate, PasswordExpired, EmailAddress, Company, Title, Manager, Office
Write-Host "9." $user "manager is" $data.manager -ForegroundColor Green
When I run the script I've got:
User's manager is CN=cool.boss,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC=com
The problem is that text "OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC=com" will be different for some users
How can I modify output and remove everything except "cool.boss"?
Thank you in advance
This should be a more or less safe and still easy way to parse it:
($data.manager -split "," | ConvertFrom-StringData).CN
To complement the helpful answers here with PowerShell-idiomatic regex solutions:
Using -split, the regex-based string splitting operator:
$dn = 'CN=cool.boss,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC=com'
($dn -split '(?:^|,)CN=|,')[1] # -> 'cool.boss'
Using -replace, the regex-based string substitution operator:
$dn = 'CN=cool.boss,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC=com'
$dn -replace '(?:^|,)CN=([^,]+).*', '$1' # -> 'cool.boss'
Note:
The above solutions do not rely on a specific order of the name-value pairs (RDNs) in the input (that is, a CN entry needn't be the first one), but they do extract only the first CN entry's value, should multiple ones be present, and they do assume that (at least) one is present.
In principle, DNs (Distinguished Names), of which the input string is an example, can have , characters embedded in the values of the name-value pairs that make up a DN, escaped as \, (or, in hex notation, \2C); e.g., "CN=boss\, cool,OU=Users,..."
A truly robust solution would have to take that into account, and would ideally also unescape the resulting value; none of the existing answers do that as of this writing; see below.
Robustly parsing an LDAP/AD DN (Distinguished Name):
The following Split-DN function:
handles escaped, embedded , chars., as well as other escape sequences, correctly
unescapes the values, which includes not just removing syntactic \, but also converting escape sequences in the form \<hh>, where hh is a two-digit hex. number representing a character's code point, to the actual character they represent (e.g, \3C, is converted to a < character).
outputs an ordered hashtable whose keys are the name components (e.g., CN, OU), with the values for names that occur multiple times - such as OU - represented as an array.
Example call:
PS> Split-DN 'CN=I \3C3 Huckabees\, I do,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC=com'
Name Value
---- -----
CN I <3 Huckabees, I do
OU {Users, SO, PL, RET…}
DC {domain, com}
Note how escape sequence \3C was converted to <, the character it represents, and how \, was recognized as an , embedded in the CN value.
Since the input string contained multiple OU and DC name-value pairs (so-called RDNs, relative distinguished names), their corresponding hashtable entries became arrays of values (signified in the truncated-for-display-only output with { ... }, with , separating the elements).
Function Split-DN's source code:
Note: For brevity, error handling and validation are omitted.
function Split-DN {
param(
[Parameter(Mandatory)]
[string] $DN
)
# Initialize the (ordered) output hashtable.
$oht = [ordered] #{}
# Split into name-value pairs, while correctly recognizing escaped, embedded
# commas.
$nameValuePairs = $DN -split '(?<=(?:^|[^\\])(?:\\\\)*),'
$nameValuePairs.ForEach({
# Split into name and value.
# Note: Names aren't permitted to contain escaped chars.
$name, $value = ($_ -split '=', 2).Trim()
# Unescape the value, if necessary.
if ($value -and $value.Contains('\')) {
$value = [regex]::Replace($value, '(?i)\\(?:[0-9a-f]){2}|\\.', {
$char = $args[0].ToString().Substring(1)
if ($char.Length -eq 1) { # A \<literal-char> sequence.
$char # Output the character itself, without the preceding "\"
}
else { # A \<hh> escape sequence, conver the hex. code point to a char.
[char] [uint16]::Parse($char, 'AllowHexSpecifier')
}
})
}
# Add an entry to the output hashtable. If one already exists for the name,
# convert the existing value to an array, if necessary, and append the new value.
if ($existingEntry = $oht[$name]) {
$oht[$name] = ([array] $existingEntry) + $value
}
else {
$oht[$name] = $value
}
})
# Output the hashtable.
$oht
}
You can use the .split() method to get what you want.
$DN = "CN=cool.boss,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC =com"
$DN.Split(',').Split('=')[1]
What i'd recommend, is throwing it into another Get-ADUser to get the displayname for neater output(:
you could use regex for that:
$s = "CN=cool.boss,OU=Users,OU=SO,OU=PL,OU=RET,OU=HBG,DC=domain,DC =com"
$pattern = [regex]"CN=.*?OU"
$r = $pattern.Replace($s, "CN=OU")
$r

Powershell: combine contents of strings with dots inbetween, ignore empty ones

Our naming convention consists of the first name, insertion, and lastname, all separated by dots. An example:
Stack Overflow = Stack.Overflow
Stack over Flow = Stack.over.flow
These outputs will be used later on in the script for the creation of a mailbox, user account, etc.
I've successfully combined the values of all strings by simply plus-ing them together, like this:
$Convention = $Firstname+"."+$Insertion+"."+$LastName
The values for these strings come from information being put in when the stript runs (Read-Host "....")
Now, I'm struggling with making this more dynamic. Of course, not every person has an insertion in their name. Using the given example, the current output of $Convention would be "Stack..Overflow", instead of "Stack.Overflow".
My question to you is: how can I filter out both, the $Insertion and the extra dot, when $Insertion is empty? It's most likely something very simple, but I can't seem to figure out what it is.
Thanks in advance for any given help!
Kr,
Robbert
I would do
$Convention = ('{0}.{1}.{2}' -f $Firstname, $Insertion, $LastName) -replace '\.+', '.'
The -replace uses regex in the first parameter, so '\.+', '.' means to replace 1 or more consecutive dots by a single dot.
Alternatively you could use regex \.{2,} which reads two or more consecutive dots
Example:
$Firstname = 'Robbert'
$Insertion = ''
$LastName = 'Verwaart'
$Convention = ('{0}.{1}.{2}' -f $Firstname, $Insertion, $LastName) -replace '\.+', '.'
Output:
Robbert.Verwaart
The code below will go through each of your $convention array, if this is an array, and test if the insertion is empty. If the $Insertion variable is empty, the $i will remove the $Insertion variable and the extra .. You need to add this into the script as a test, before creating the mailboxes.
foreach ($i in $convention){
if($insertion -eq "" -or $insertion -eq $null) {
$i= $Firstname+"."+$LastName
} else {
continue
}
}

Read a CSV in powershell with a variable number of columns

I have a CSV that contains a username, and then one or more values for the rest of the record. There are no headers in the file.
joe.user,Accounting-SG,CustomerService-SG,MidwestRegion-SG
frank.user,Accounting-SG,EastRegion-SG
I would like to read the file into a powershell object where the Username property is set to the first column, and the Membership property is set to either the remainder of the row (including the commas) or ideally, an array of strings with each element containing a single membership value.
Unfortunately, the following line only grabs the first membership and ignores the rest of the line.
$memberships = Import-Csv -Path C:\temp\values.csv -Header "username", "membership"
#{username=joe.user; membership=Accounting-SG}
#{username=frank.user; membership=Accounting-SG}
I'm looking for either of these outputs:
#{username=joe.user; membership=Accounting-SG,CustomerService-SG,MidwestRegion-SG}
#{username=frank.user; membership=Accounting-SG,EastRegion-SG}
or
#{username=joe.user; membership=string[]}
#{username=frank.user; membership=string[]}
I've been able to get the first result by enclosing the "rest" of the data in the csv file in quotes, but that doesn't really feel like the best answer:
joe.user,"Accounting-SG,CustomerService-SG,MidwestRegion-SG"
Well, the issue is that what you have isn't really a (proper) CSV. The CSV format doesn't support that notation.
You can "roll your own" and just process the file yourself, something like this:
$memberships = Get-Content -LiteralPath C:\temp\values.csv |
ForEach-Object -Process {
$user,$membership = $_.Split(',')
New-Object -TypeName PSObject -Property #{
username = $user
membership = $membership
}
}
You could do a half and half sort of thing. Using your modification, where the groups are all a single field in quotes, do this:
$memberships = Import-Csv -Path C:\temp\values.csv -Header "username", "membership" |
ForEach-Object -Process {
$_.membership = $_.membership.Split(',')
$_
}
The first example just reads the file line by line, splits on commas, then creates a new object with the properties you want.
The second example uses Import-Csv to create the object initially, then just resets the .membership property (it starts as a string, and we split the string so it's now an array).
The second way only makes sense if whatever is creating the "CSV" can create it that way in the first place. If you have to modify it yourself every time, just skip this and process it as it is.

What's the proper way to iteratively grab a value from an array when the key is a string?

I'm trying to export some lists from Sharepoint into a CSV file. My goal is to make a single function that is flexible enough to take a List name, identifier for the CSV file, and a list of FieldValues to export, and then produce a CSV file. Here's what I have so far:
function getTableData($_ctx, [string]$_colName)
{
$list = $_ctx.Web.Lists.GetByTitle("$_colName")
$camlQuery = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(100)
$colItems = $list.GetItems($camlQuery)
$_ctx.Load($colItems)
$_ctx.ExecuteQuery();
return $colItems
}
# More will go into this array, but for now a single entry is sufficient for testing purposes
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[0][2])
{
$cols += #{Name=$col;expression={$_[$col];}}
}
$cols
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop $cols } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
The problem I'm having is in the loop that populates $cols. Basically, each item needs to look like #{Name="Title";expression={$_["Title"];}} in order for select-object in the ForEach to grab the proper fields from the List. Unfortunately $cols ends up being looking like this:
Name Value
---- -----
expression $_[$col];
Name Title
expression $_[$col];
Name GroupCode
Which (somehow) produces a CSV file that looks like this:
"Title","GroupCode"
"LA","LA"
"NY","NY"
"TK","TK"
When the output needs to look like this:
"Title","GroupCode"
"Los Angeles","LA"
"New York","NY"
"Tokyo","TK"
I know the field names are correct - if I hardcode them in like so...
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop `
#{Name="Title";expression={$_["Title"];}}, `
#{Name='GroupCode';expression={$_["GroupCode"];}}; } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
...then I get the desired CSV output. I just can't figure out how to get $_[$col] to instead return $_["Title"]
Not sure what the $mstLists[0][2] meant to refer to, but the following code seems to give what you are after...
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[2])
{
$cols += #{Name=$col; Expression = [scriptblock]::Create('$_["{0}"]' -f $col)}
}
$cols
which gives...
Name Value
---- -----
Name Title
Expression $_["Title"]
Name GroupCode
Expression $_["GroupCode"]
In your response to andyb in the comments, you say that each item of the array will follow the format
#("ListName", "CSVFileID", #("Field1", "Field2", "Etc..."))
and that $mstLists[0][2] "refers to the list of fields in the first item in the array."
The problem is that it doesn't refer to the list of fields in the first item of the array, because the first item of the array isn't a list of anything, it's the string GroupMst. When you index into a string, you get the character indicated by the index. Since $mstLists[0] is a string, $mstLists[0][2] returns the third character of that string, which is o.
I suppose you were expecting that the # operator would make the array in the parentheses a single item, which becomes the first element of $mstLists? It doesn't. All the # does is ensure that the expression in the parentheses is evaluated as an array rather than a scalar. So, with $a = ('string'), $a is a string, whereas with $a = #('string'), $a is an array with a single string element.
However, since ("GroupMst", "Groups", #("Title", "GroupCode")) evaluates to an array anyway, putting an # in front of it is redundant. Either way you're still assigning a literal array to the variable. $mstLists is an array of three elements:
Element 0 is the string GroupMst
Element 1 is the string Groups
Element 2 is an array of the strings Title and GroupCode
What you want to do is use , as a unary operator:
$mstLists = , ("GroupMst", "Groups", #("Title", "GroupCode"))
Now $mstLists is an array of a single item whose value is the array described in the bulleted list above, and $mstLists[0][2] evaluates to an array of the strings Title and GroupCode, as you were expecting.
Note that , works as a unary or binary operator that returns an array of the operands. To return a single-element array, you use it as a unary operator in front of that element. If you have multiple literal arrays that you want to assign to mstLists, you only need commas between them, not the one in front:
$mstLists = ("ListName", "CSVFileID", #("Field1", "Field2", "Etc...")), ("ListName2", "CSVFileID2", #("Field1", "Field2", "Etc..."))
That addresses the main problem. That still won't quite give you what you want, because $col won't get interpolated in the Expression scriptblock, so Expression will always be literally $_[$col]. However, in order to figure out how to do what you actually want to do, it would be helpful to see a sample of the contents of $ctx.