4

I need to verify MAC address in RAW format using RegEx and split it into an array of 6 values by 2 characters.
When I use following pattern, I get content of last iteration of capture group only:

PS C:\Windows\System32> "708BCDBC8A0D" -match "^([0-9a-z]{2}){6}$"
True
PS C:\Windows\System32> $Matches

Name                           Value
----                           -----
1                              0D
0                              708BCDBC8A0D


PS C:\Windows\System32>

With what pattern can I caputere all the groups?
I need this result:

0 = 708BCDBC8A0D
1 = 70
2 = 8B
3 = CD
4 = BC
5 = 8A
6 = 0D
rga.cz
  • 53
  • 6

2 Answers2

1

You can not capture multiple groups with single group definition. Avoid using RegEx when unnecessary as it takes lots of CPU. Valuable for millions of recrds.

For MACs you can use special PhysicalAddress class:

[System.Net.NetworkInformation.PhysicalAddress]::Parse('708BCDBC8A0D') 

For .Net 5 (Powershell Core I think based on it) there is TryParse method added, but in .Net 4.5 there is no TryParse method.

To check .Net framework powershell running use [System.Reflection.Assembly]::GetExecutingAssembly().ImageRuntimeVersion


'708BCDBC8A0D' -match "^$('([A-F0-9]{2})' * 6)$"; $Matches
'708BCDBC8A0D' -match '^([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})$'; $Matches

'@(0..5) | ForEach-Object {'708BCDBC8A0D'.Substring($_ * 2, 2)}'

@(
    [String]::new('708BCDBC8A0D'[0..1]),
    [String]::new('708BCDBC8A0D'[2..3]),
    [String]::new('708BCDBC8A0D'[4..5]),
    [String]::new('708BCDBC8A0D'[6..7]),
    [String]::new('708BCDBC8A0D'[8..9]),
    [String]::new('708BCDBC8A0D'[10..11])
)
filimonic
  • 3,988
  • 2
  • 19
  • 26
  • I am aware about higher CPU load when using RegEx, but I need it for validating MAC address anyway. "Hack" with `* 6` is good not to repeat definition. And this PowerShell way `@(0..5) | ForEach-Object {'708BCDBC8A0D'.Substring($_ * 2, 2)}` too. Thanks for hints! – rga.cz Mar 22 '21 at 09:22
  • 1
    Maybe using `[System.Net.NetworkInformation.PhysicalAddress]::Parse('708BCDBC8A0D')` is better in your case ? – filimonic Mar 22 '21 at 09:29
  • As for the .NET versions underlying PowerShell versions: as of the current stable version, PowerShell 7.1.3, it is .NET 5.0.4. v7.2 will likely be based on .NET 6.0 (not released yet). You can run the following command in a PowerShell (Core) session to determine the underlying .NET version(s): `(ConvertFrom-Json (Get-Content -Raw "$PSHOME/pwsh.runtimeconfig.json")).runtimeOptions.includedFrameworks.version` – mklement0 Mar 26 '21 at 16:08
0

As you've observed, the automatic $Matches variable, which reflects the result of the most recent (scalar-input[1]) regular-expression-based match operation, only ever contains the last instance of what an embedded capture group ((...)) captured.

Generally, -match only ever looks for at most ONE match in the input.

  • GitHub issue #7867 proposes introducing a new -matchall operator that would find all matches and return them as an array.

Direct use of the [regex] class (System.Text.RegularExpressions.Regex) that underlies PowerShell's regex functionality already provides that ability, namely in the form of the ::Matches() method, in which case capture groups aren't even needed.

# Note: Inline option (?i) makes the regex case-INsensitive
#       (which PowerShell's operators are BY DEFAULT).
PS> [regex]::Matches('708BCDBC8A0D', '(?i)[0-9a-f]{2}').Value
70
8B
CD
BC
8A
0D

However, with a bit of trickery, you can also use -split, the string splitting operator:

# Note: No inline option needed: -split - like -match and -replace -
#       is case-INsensitive by default.
PS> '708BCDBC8A0D' -split '([0-9a-f]{2})' -ne ''
70
8B
CD
BC
8A
0D

If can assume that all character pairs in the input strings are hex byte values, you can simplify to:

'708BCDBC8A0D' -split '(..)' -ne ''

Note:

  • The regex is of necessity enclosed in (...), a capturing group, to explicitly instruct -split to include what it matches in the results; since the regex normally describes the separators between the substrings of interest, its matches are normally not included.

  • In this case it is only the "separators" we care about, whereas the substrings between them are empty strings here, so we filter them out with -ne ''.


[1] If the LHS of a -match operation is an array (a collection), matching occurs against each element, and the sub-array of matching elements (rather than a single Boolean) is returned. In this case, $Matches is not populated.

mklement0
  • 382,024
  • 64
  • 607
  • 775