Dan
Dan

Reputation: 23

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.

Upvotes: 1

Views: 254

Answers (3)

mklement0
mklement0

Reputation: 437953

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.

Upvotes: 1

MikeM
MikeM

Reputation: 13631

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] }

Upvotes: -1

Santiago Squarzon
Santiago Squarzon

Reputation: 60045

Instead of trying to parse the output from net user USER /domain I would use what's already available in . 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.

  1. 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]
}
  1. 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]
}

Upvotes: 1

Related Questions