lazywinadmin / lazywinadmin.github.io

Personal Blog - Using the minimal-mistakes jekyll template
https://lazywinadmin.com
MIT License
4 stars 3 forks source link

2013/10/powershell-get-substring-out-of-string #193

Closed lazywinadmin closed 5 years ago

lazywinadmin commented 5 years ago

PowerShell - Get a SubString out of a String using RegEx

https://lazywinadmin.com/2013/10/powershell-get-substring-out-of-string.html

lazywinadmin commented 5 years ago

Zachary Loeber (@zloeber)

Posted on: 2013-10-20T06:29:18Z

Good stuff! Here are some functions for processing ldap paths you may like. I hid them in a recent script release (but never actually used them, like a hidden little gift).

Function Get-TreeFromLDAPPath
{
# $Output = [System.Web.HttpUtility]::HtmlDecode(($a | ConvertTo-Html))
[CmdletBinding()]
Param
(
[Parameter(HelpMessage="LDAP path.")]
[string]
$LDAPPath,

[Parameter(HelpMessage="Determines the depth a tree node is indented")]
[int]
$IndentDepth=1,

[Parameter(HelpMessage="Optional character to use for each newly indented node.")]
[char]
$IndentChar = 3,

[Parameter(HelpMessage="Don't remove the ldap node type (ie. DC=)")]
[Switch]
$KeepNodeType
)
$regex = [regex]'(?^.+)\=(?.+$)'
$ldaparr = $LDAPPath -split ','
$ADPartCount = $ldaparr.count
$spacer = ''
$output = ''
for ($index = ($ADPartCount); $index -gt 0; $index--)
{
$node = $ldaparr[($index-1)]
if (-not $KeepNodeType)
{
if ($node -match $regex)
{
$node = $matches['LDAPName']
}
}
if ($index -eq ($ADPartCount))
{
$line = ''
}
else
{
$line = $IndentChar
$spacer = $spacer + (' ' * $IndentDepth)
# This fixes an offset issue
if ($index -lt ($ADPartCount - 1))
{
$spacer = $spacer + ' '
}
}
$line = $spacer + $line + $node + "`n"
$output = $Output+$line
}
[string]$output
}

Function Get-ObjectFromLDAPPath
{
[CmdletBinding()]
Param
(
[Parameter(HelpMessage="LDAP path.")]
[string]
$LDAPPath,

[Parameter(HelpMessage="Determines the depth a tree node is indented")]
[switch]
$TranslateNamingAttribute
)
$output = @()
$ldaparr = $LDAPPath -split ','
$regex = [regex]'(?^.+)\=(?.+$)'
$position = 0
$ldaparr | %{
if ($_ -match $regex)
{
if ($TranslateNamingAttribute)
{
switch ($matches['LDAPType'])
{
'CN' {$_ldaptype = "Common Name"}
'OU' {$_ldaptype = "Organizational Unit"}
'DC' {$_ldaptype = "Domain Component"}
default {$_ldaptype = $matches['LDAPType']}
}
}
else
{
$_ldaptype = $matches['LDAPType']
}
$objprop = @{
LDAPType = $_ldaptype
LDAPName = $matches['LDAPName']
Position = $position
}
$output += New-Object psobject -Property $objprop
$position++
}
}
Write-Output -InputObject $output
}

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-20T07:19:14Z

Thanks Zachary! I will check it out

lazywinadmin commented 5 years ago

Jay

Posted on: 2013-10-20T18:15:04Z

Another way of using Regex here is to use these two lines:
'OU=MTL1,OU=CORP,DC=FX,DC=LAB' -match '(?<=(^OU=))(?=(,))'
$matches[0]

This uses the regex concepts of lookahead and lookbehind which are covered fairly well in this article:
http://blogs.technet.com/b/...

This matches the string that comes after the pattern '^OU='(the caret '^' is the beginning of line metacharacter) and before the pattern ','. The -match operator returns a boolean and stores the actual matches in the $matches variable.

Splitting on ',*..=' requires you to know where substring is in the string in order to choose the correct index from the array. If that's the case the substring method is going to be more straightforward than regex. If you're not sure where in the string the pattern is you're better off using regex.

lazywinadmin commented 5 years ago

Jay

Posted on: 2013-10-20T18:22:52Z

Another useful link on Regular Expressions in .net:
Regular Expression Language - Quick Reference
http://msdn.microsoft.com/e...

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-20T20:56:58Z

Thanks Jay, Useful !
I added the link to the post.

lazywinadmin commented 5 years ago

Luis Yax

Posted on: 2013-10-20T23:21:43Z

Here is another way, except that with this method you can select column 1 or column 2 at the same time side by side, take a look:

$var1 = "OU=MTL1,OU=CORP,DC=FX,DC=LAB"

This gives column 1
$var1 | % {"{1}" -f($_ -split ',*..=')}
MTL1

Now you want column 1 and 3

$var1 | % {"{1} {3}" -f($_ -split ',*..=')}
MTL1 - FX

if you want a separator of some kind, the dash in this case, "-":

$var1 | % {"{4} - {1}" -f($_ -split ',*..=')}
FX - MTL1

Enjoy!

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-21T00:36:54Z

Thanks Jay, weird i'm getting an error with
'OU=MTL1,OU=CORP,DC=FX,DC=LAB' -match '(?<=(^OU=))(?=(,))'
$matches[0]

The first line return $false.

Thanks again for the information, really useful! very appreciated

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-21T00:43:11Z

Awesome! Thanks Luis, Great info!
It's actually faster with your line

PS C:\Users\Francois-Xavier> Measure-Command { ($var1 -split ',*..=')[1]}


Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 1
Ticks : 16121
TotalDays : 1.86585648148148E-08
TotalHours : 4.47805555555556E-07
TotalMinutes : 2.68683333333333E-05
TotalSeconds : 0.0016121
TotalMilliseconds : 1.6121


PS C:\Users\Francois-Xavier> Measure-Command { $var1 | % {"{1}" -f($_ -split ',*..=')}}


Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 0
Ticks : 5494
TotalDays : 6.3587962962963E-09
TotalHours : 1.52611111111111E-07
TotalMinutes : 9.15666666666667E-06
TotalSeconds : 0.0005494
TotalMilliseconds : 0.5494

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-21T06:26:18Z

I added a couple of links at the bottom of the post. The Scripting Guy Ed Wilson and Lee Holmes wrote some nice articles about the subjet, hope this help!

lazywinadmin commented 5 years ago

Jay

Posted on: 2013-10-22T04:45:30Z

My apologies the first line should be:
'OU=MTL1,OU=CORP,DC=FX,DC=LAB' -match '(?<=(^OU=))\w*(?=(,))'

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2013-10-22T04:55:21Z

Nice!! Thanks Jay :-)

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-04-14T19:53:05Z

I'm trying to use this to strip off the first 3 digits in a phone number report I have to do, so I need to query AD, get all user names & phone numbers, then strip off/delete the 1st 3 digits, and then export and email the csv. Any idea's how to do that reliably?

disqus profile

lazywinadmin commented 5 years ago

Ernie Hinkle

Posted on: 2014-04-21T21:03:07Z

What if you only want to get the first OU value after DC=, in this example Corp from the string. Also where you could have something like CN=PC-Name,OU=Sales,OU=Chicago,DC=Central,DC=FXCorp,DC=com

CN=PC-Name,OU=Sales,OU=LA,DC=West,DC=FXCorp,DC=com

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2014-04-21T21:09:37Z

Hi Ernie,

What about :
("CN=PC-Name,OU=Sales,OU=LA,DC=West,DC=FXCorp,DC=com" -split ",")[4].substring(3)

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2014-04-21T21:26:55Z

Hi Marie,

For the AD query and trim of 3 first digits, this should do it:

https://gist.github.com/laz...

disqus profile

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-04-24T18:33:24Z

Thanks, that's great! My only problem is I should have mentioned I don't have an AD Web proxy so I have to use the Quest Cmdlets, can I do this using them? Then take the output of usernames and phone numbers, export to a network path or email a csv file?

Thank you so much!!
Marie

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2014-04-24T18:54:52Z

Hi Marie, the script I shared with you use ADSI, so you dont need any module or snapin like Quest Cmdlets. It should just work fine

Exporting the output can just be done using Export-CSV -path exported_users.csv

Example:
Get-DomainUser -DisplayName "*" | Export-CSV -path c:\exported_users.csv

disqus profile

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-04-30T05:01:03Z

Thank you Francois! I did not realize at first this was using ADSI, we have a 2003 domain and I don't have an AD Web service running and that leaves me struggling often, hence why I rely on the Quest AD Module.

I do get the Username "SAMAccountName", Description, Display Name, and Telephone Number. The number outputs without the 1st three digits as I asked how to do, but I am left with the number as "-123-4567". How can I remove that leading "-" symbol?

I then get this error and I think it's because I am searching the entire domain, how can I limit this to a single OU?

Exception calling "Substring" with "1" argument(s): "startIndex cannot be larger than length of string.

Parameter name: startIndex"

At C:\scripts\list.ps1:6 char:9

+ New-Object -TypeName PSObject -Property @{

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ArgumentOutOfRangeException

disqus profile

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-05-05T13:20:27Z

Hi Francois, is there any way to do what I want that's not going to be a big challenge?

I've spend a few days banging my head off the wall and can't figure this out

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2014-05-06T17:10:28Z

Hi Marie,

You just need to change the substring value.

see here, this should do it

https://gist.github.com/laz...

disqus profile

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-05-08T18:25:48Z

This is great, I need to narrow the scope to only 1 OU and all the SUB-OUs under that though, is it possible?
Then I just have to export that to a csv file and run this as a scheduled task.

disqus profile

lazywinadmin commented 5 years ago

Marie S

Posted on: 2014-05-13T19:23:04Z

I found some ways to filter and query only an OU, but nothing is working right if I try to narrow the search scope.

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2014-05-17T16:54:38Z

Hi Marie,

This should do it

https://gist.github.com/laz...

You need to specify the DistinguishedName of the OU

disqus profile

lazywinadmin commented 5 years ago

Stan

Posted on: 2016-04-17T20:52:42Z

I happen to notice your discussion on variables and substrings.
Say I have a txt file and I want to compare first two substrings on each line of the file.
The two substrings are separated by a + sign only. So how do I get to compare substring1 with substring 2 on each line knowing that these substrings are only separated by a + operator, and count the occurrence of a match of these two substring1 substring2 on all the lines of the txt file?
Thanks

disqus profile

lazywinadmin commented 5 years ago

Stan

Posted on: 2016-04-17T21:55:31Z

Or search substrings in an array of patterns; the array is made of 50000 lines of text. The substrings are delimited/separated by a plus sign on each line and I want to compare substring1 with substring2 on each line...

disqus profile

lazywinadmin commented 5 years ago

Tom Emv

Posted on: 2017-04-17T12:17:01Z

Hi. Good tip! Thnx.
But what can I do if my output is two strings?
Example:
Input: Get-ADDomain | Select ComputersContainer
Output:
ComputersContainer
------------------
CN=Computers,DC=contoso,DC=com

How I can select only DC=contoso,DC=com in new variables?

disqus profile

lazywinadmin commented 5 years ago

François-Xavier Cat (@lazywinadmin)

Posted on: 2017-04-18T02:31:33Z

Hi Tom, not sure what you are trying to do...
You can do something like that:
((Get-ADDomain).ComputersContainer -split ',')[-2,-1] -join ','

disqus profile

lazywinadmin commented 5 years ago

Tom Emv

Posted on: 2017-06-17T21:30:08Z

I did so
$dname = Get-ADDomain | % { $_.Forest }
$dname = $dname.Split("{.}")

And my final solution looks like https://github.com/getsuein...

disqus profile

burney8 commented 5 years ago

I just did $Var.Split(",")[0].Replace"OU=" "")

Intrudr-Sec commented 4 years ago

Salut François-Xavier,

Tres bon post, sa aide beaucoup. mais j'ai une question. Comment je peux inverser le CN d'un manager d'usager, lorsqu'il est formater "Lastname,\ Firstname" ? J'ai besoin d'avoir comme resultat "Firstname" + " " + "Lastname"

Voici mon resultat : PS C:\WINDOWS\system32> $DN = 'CN=Dummy\, Test,OU=Users,OU=Montreal,DC=lab,DC=com'

PS C:\WINDOWS\system32> $DN | % {"{1}" -f($_ -split ',*..=')} Dummy\, Test

Merci de ton aide.

Intrudr-Sec commented 4 years ago

voici ma trouvaille, $DN | % {"{2} {1}" -f(($_ -split ',*..=') -replace "\,","").split(" ")}

reste a voir comment l'integrer pour faire une variable de Get-ADUser -properties manager

je dois avoir dans le style @{label='Manager';expression={$_.manager }} mais que celui soit deja corriger

Intrudr-Sec commented 4 years ago

@{Name="4";Expression={% {"{2} {1}" -f(($_.manager -split ',*..=') -replace "\,","").split(" ")}}},

Ackep1 commented 1 year ago

my best practice $var = ("OU=MTL1,OU=CORP,DC=FX,DC=LAB".Split(",")[0]).Split("=")[1]

VandersonsSquad commented 1 year ago

Hello there,

this topic might be an older one, but I thought, I could still contribute something here.

I have tested the Regex user recommendations. Jay's one falls short, since it seems to only returns the very first OU=. But "MTL1" might not be the first one. Roberts approach also seems to calculate with the assumption, OU=MTL1 will be the first one. Using the naming group capture is a nice touch, but it returns OU=CORP with $matches["MTL1"], if CORP came first. Additionally, both have the problem to match also MTL11 as valid.

If you like to use a full string Regex for the sake of it, then this is my recommendation: (?:|.,)OU=(MTL1)(?:,.|) $matches[1]

First, you need your string to start with "Nothing" or with "any char followed by comma". Then you want "OU=MTL1", right? If you like MTL1 as a separated match value for later access, use "OU=(MTL1)". Finally you want the string followed by "nothing" or "a comma followed by anything".

In this case Match[1] will always be "MTL1" or $null, if not found. If you like to access this value like Robert did ($matches["MTL1"]), use this expanded line: "(?:|.,)OU=(?MTL1)(?:,.|)"

This solution will match MTL1 at any position, it won't match MTL11.

Since I like readable code, I do things like this to separate the wierd stuff from the actual information: $StartWith_nothing_OR_AnythingAndComma = "^(?:|.,)" $CloseWith_nothing_OR_CommaAndAnything = "(?:|,.)$"

'OU=CORPO,OU=MTL1,OU=MTL2,OU=MTL11,OU=CORP,DC=FX,DC=LAB' -match $( "${StartWith_nothing_OR_AnythingAndComma}"

VandersonsSquad commented 1 year ago

Otherwise: The cleanest solution doesn't involve so much of sophisticated Regex-Queriing:

$AD_ArrayList = "OU=MTL1,OU=CORP,DC=FX,DC=LAB" -split "," $ADArrayList | Where { $ -match '=MTL1$' }

Result is $null or "OU=MTL1" so many times as it could be found inside the array. It is easy from here on.

If you like to read only MTL1, then just add: ... | foreach-object {$_ -replace('OU=','')}

If you just wanna know, if it matches, use (-eq or -ne): $null -eq $( $ADArrayList | Where { $ -match '=MTL1$' } )

Don't overcomplicate things so much. The reader of your sourcecode, which might probably you yourself in a few weeks, will be thankful.

VandersonsSquad commented 1 year ago

Oh dear, it is markdown. Sorry, here is the full Expression "(?:|.*,)OU=(?\<MTL1>MTL1)(?:,.*|)"

VandersonsSquad commented 1 year ago

Oh, I'm so sorry. While being occupied with solving my own problem, I completely missed the issue with this target. Please delete my posts above, if you like so and are able to.

Assuming, the wanted sitecode can always be found at the beginning of the string and returning the name of the sitecode is the target, this is my solution.

... -match "^OU=(?\<SiteCode>\w+)(?:,.*|)$" $matches['SiteCode'] would give the result.

It modifies Robert's recommendation so far, that the characters after OU= must be of type \w+ (Word: [A-Za-z0-9_] Furthermore it says, Word is followed by "a comma and anything" or "nothing", encapsulated in a "please count me as a match-bracket -> (?: )"

This notation may raise the readability, especially, if you want to know, what happens without needing to know every bit of detail:

$siteCode = "(?\<SiteCode>\w+)" $FollowedBy_commaAndAnything_or_Nothing = "(?:,.*|)"

... match "^OU=${SiteCode}${FollowedBy_commaAndAnything_or_Nothing}$"