I want to write PowerShell script and regex to audit several network devices configuration files for compliance. Some devices are configured one management vlan while others have multiple different management vlans. Examples below
Config1:
VLAN Name Status Ports
1 default active
100 12_NET_MGMT_VLAN active Gi1/2
Config2:
VLAN Name Status Ports
1 default active
88 100_MGMT-VLLAN active Gi8/1
100 12_Net_MGMT_VLAN active
If I hard code the regex pattern like this $regex_pattern = "^\d{1,3}\s+.*MGMT.*", I got the corrected output as expected
Config1 12_NET_MGMT_VLAN
Config2 100_MGMT_VLAN
Config2 12_Net_MGMT_VLAN
Instead of hard-code the regex pattern, I want to use the Read-Host cmdlet and ask a user to enter the word "MGMT" and store it in a variable $Mgmt, then concatenate with a regex pattern to create a dynamic regex pattern, like this:
$Mgmt = Read-Host "Enter a word pattern to find a management vlan: "
For example, a user type in MGMT, and then I created a dynamic regex pattern as below:
$regex_pattern = "^\d{1,3}\s+.*"+$Mgmt+"_.*"
$regex_pattern = "^\d{1,3}\s+.*"+[regex]::escape($Mgmt)+".*"
None of the results came out correct
If anyone has a solution, please help. Thx
If we are to assume that VLAN names cannot contain spaces, you can use \S (non-space) as an anchor character. Using the subexpression operator $(), you can evaluate an expression within a string.
# Simulating a vlan config output
$Config = #'
VLAN Name Status Ports
1 default active
88 100_MGMT-VLLAN active Gi8/1
100 12_Net_MGMT_VLAN active
'# -split '\r?\n'
# Using value MGMT here when prompted
$Mgmt = Read-Host "Enter a word pattern to find a management vlan"
$regex = "\S*$([regex]::Escape($Mgmt))\S*"
[regex]::Matches($Config,$regex).Value
Output:
100_MGMT-VLLAN
12_Net_MGMT_VLAN
Note that simple variable references like $Mgmt will expand properly within surrounding double quotes, e.g. "My VLAN is $Mgmt".
You could take this in a different direction and create a custom object from your output. This would enable you to use filtering via Where-Object and member access (.Property) to retrieve target data. This again assumes values don't contain spaces.
# Simulating a vlan config output
$Config = #'
VLAN Name Status Ports
1 default active
88 100_MGMT-VLLAN active Gi8/1
100 12_Net_MGMT_VLAN active
'# -split '\r?\n'
$Mgmt = Read-Host "Enter VLAN Name"
# Replacing consecutive spaces with , first
$ConfigObjs = $Config -replace '\s+',',' | ConvertFrom-Csv
$ConfigObjs
Output:
VLAN Name Status Ports
---- ---- ------ -----
1 default active
88 100_MGMT-VLLAN active Gi8/1
100 12_Net_MGMT_VLAN active
Now you have properties that can be referenced and access to other comparison operators so you don't always need to use regex.
($ConfigObjs | Where Name -like "*$Mgmt*").Name
Output:
100_MGMT-VLLAN
12_Net_MGMT_VLAN
Related
I would like to extract the values for connections, upstream and downstream using telegraf regex processor plugin from this input:
2022/11/16 22:38:48 In the last 1h0m0s, there were 10 connections. Traffic Relayed ↑ 60 MB, ↓ 4 MB.
Using this configuration the result key "upstream" is a copy of the initial message but without a part of the 'regexed' stuff.
[[processors.regex]]
tagpass = ["snowflake-proxy"]
[[processors.regex.fields]]
## Field to change
key = "message"
## All the power of the Go regular expressions available here
## For example, named subgroups
pattern = 'Relayed.{3}(?P<UPSTREAM>\d{1,4}\W.B),'
replacement = "${UPSTREAM}"
## If result_key is present, a new field will be created
## instead of changing existing field
result_key = "upstream"
Current output:
2022/11/17 10:38:48 In the last 1h0m0s, there were 1 connections. Traffic 3 MB ↓ 5 MB.
How do I get the decimals?
I'm quite a bit confused how to use the regex here, because on several examples in the web it should work like this. See for example: http://wiki.webperfect.ch/index.php?title=Telegraf:_Processor_Plugins
The replacement config option specifies what you want to replace in for any matches.
I think you want something closer to this:
[[processors.regex.fields]]
key = "message"
pattern = '.*Relayed.{3}(?P<UPSTREAM>\d{1,4}\W.B),.*$'
replacement = "${1}"
result_key = "upstream"
to get:
upstream="60 MB"
WorkstationList.csv has 3 separate columns with different names. Trying to use a column "retired" that has a list of retired logonworkstations names and match them to a users current list. If there is a match then delete from the users logonworkstation list.
$defaultWorkstationslist = Import-Csv -Path '[workstationList.csv]'
$olist = Get-Aduser $user-Properties LogonWorkstations | Select LogonWorkstations
$newlist = ''
foreach ($o in $olist){
foreach ($r in $defaultWorkstationslist.retired){
if ($o -ne $r){
$newlist += $o
} else {
continue
}
}
}
Set-ADUser $user -logonWorkstations $newlist
Output:
Set-ADUser : The format of the specified computer name is invalid
[redacted]:36 char:1
+ Set-ADUser $user-logonWorkstations $newlist
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (user:ADUser) [Set-ADUser], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:1210,Microsoft.ActiveDirectory.Management.Commands.SetADUser
According to the Set-ADUser Docs for this specific parameter:
To specify more than one computer, create a single comma-separated list. You can identify a computer by using the Security Account Manager (SAM) account name (sAMAccountName) or the DNS host name of the computer. The SAM account name is the same as the NetBIOS name of the computer.
We can assume that, if the user has this property set, it would be a string with each computer comma separated (computer1,computer2...). Following that assumption, we can first test if the user has that property set, then split the value by comma and lastly filter each value against the $defaultWorkstationslist.retired array.
Important Note, this should work as long as the column Retired from your Csv has computer names using the SAM account name or NetBIOS name of the computer as stated in the docs.
$user = 'someuser'
$defaultWorkstationslist = Import-Csv -Path '[workstationList.csv]'
$aduser = Get-ADUser $user -Properties LogonWorkstations
# if this user has the attribute set
if($wsList = $aduser.LogonWorkstations) {
# split the string by comma and then use filtering technique to exclude those values that
# exists in the `$defaultWorkstationslist.retired` array.
$allowedList = ($wsList.Split(',').Where{ $_ -notin $defaultWorkstationslist.retired }) -join ','
Set-ADUser $user -LogonWorkstations $allowedList
}
For filtering we can use the .Where intrinsic method, where the current object in the pipeline is represented with $_ ($PSItem)
For testing if an element $wsList is contained in $defaultWorkstationslist.retired we can use Containment operators.
I need to modify the below code with some International Phone area code formatting from the Active Directory without modifying the actual AD attribute value:
$defaultTelephone = '1800 552 001'
#Get Active Directory information for the currently logged on user
$sysInfo = New-Object -ComObject 'ADSystemInfo'
$userDN = $sysInfo.GetType().InvokeMember('UserName', 'GetProperty', $null, $sysInfo, $null)
$adUser = [ADSI]"LDAP://$($userDN)"
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($sysInfo)
#Get the phone number from the Active Directory and assign it into the International phone country code format
$IntlPhoneNumber = $(If ($ADUser.telephoneNumber) { $ADUser.telephoneNumber.ToString() }
Else { $defaultTelephone })
$IntlPhoneNumber
in the above script, it pulls the Information As is from the AD Attributes which is now set 08 8211 8911
What I wanted to display as the value of $IntlPhoneNumber is + 1 8 8211 8911
So I need to:
Add +1 as Country code
Remove 0 from the variable but not removing or modifying the Active Directory value.
If the phone number is NOT in the form of 2digits 4digits 4digits, then display it as is no need to change into +1 Country Code and removing the zero.
After reading the number from Active Directory, check if it should be changed and do it if necessary. Like this, the number won't be changed in Active Directory (there is no write operation anyway):
$IntlPhoneNumber = "08 8211 8911"
if($IntlPhoneNumber -match '^\d{2}(\s\d{4}){2}$'){
$IntlPhoneNumber = $IntlPhoneNumber -replace '^0', '+1 '
}
$IntlPhoneNumber # +1 8 8211 8911
The RegEx ^\d{2}(\s\d{4}){2}$ matches only with telephone numbers with the format 2digits 4digits 4digits.
Can someone tell me what I'm doing wrong in my regexp statement? It doesn't match the "Operability: Degraded" line. I am trying to match anything that is not in operable state. I am new to TCL. Thanks!
Contents of $expect_out(buffer) it does the regexp on:
ID 20:
Location: G1
Presence: Equipped
Overall Status: Operable
Operability: Degraded
Visibility: Yes
Product Name: 16GB DDR3-1600-MHz RDIMM/PC3-12800/dual rank/1.35V
PID:
VID: V01
Vendor: 0x2C00
Vendor Description: Micron Technology, Inc.
Vendor Part Number:
Vendor Serial (SN):
HW Revision: 0
Form Factor: DIMM
Type: DDR3
Capacity (MB): 16384
Clock: 1600
Latency: 0.600000
Width: 64
Code:
proc check_errors { buffer cmd } {
set count [ regexp -all -- { Activate-Status.*?!Ready|Overall.*Status.*?!Operable|Operability.*?!Operable|Controller.*Status.*?!Optimal|Errors.*?!0|Dr
opped.*?!0|Discarded.*?!0|Bad.*?!0|Suspect.*?!No|Thresholded.*?!0|Visibility.*?!Yes|Thermal.*Status.*?!OK|HA.*?!READY } $buffer ]
if { [ set count ] != 0 } {
puts "\tFAIL $cmd (Error Count: $count)"
} else {
puts "\tPASS $cmd"
}
}
Output: (blade 6/5 has a known issue, it should fail the memory check)
Blade 6/5 checks...
PASS show stats
PASS show version
PASS show adapter detail
PASS show cpu detail
PASS show memory detail
PASS show inventory detail
!term doesn't mean "anything but term" in regex. For that type of logic, you'll need a negative lookahead approach:
Activate-Status(?!.*Ready)|Overall.*Status(?!.*Operable)|Operability(?!.*Operable)|Controller.*Status(?!.*Optimal)|Errors(?!.*0)|Dropped(?!.*0)|Discarded(?!.*0)|Bad(?!.*0)|Suspect(?!.*No)|Thresholded(?!.*0)|Visibility.(?!.*yes)|Thermal.*Status(?!.*OK)|HA.*(?!.*READY)
check it out here
note: I'd use case insensitivity to filter out both "No" and "no", and also, you must make sure your input is not treated as a single line, but multiple lines, so the .* wildcards don't race past the \n newlines and mess everything up.
#sweaver2112 has the right answer. I'd like to add maintainability into the mix:
use the -expanded flag for additional non-meaningful whitespace
use the -line so . does not match a newline (so "Ready" is on the same line as "Activate-Status")
-nocase for case-insensitive matching (if that's important)
set count [ regexp -all -expanded -line -- {
Activate-Status (?!.*?Ready) |
Overall.*Status (?!.*?Operable) |
Operability (?!.*?Operable) |
Controller.*Status (?!.*?Optimal) |
Errors (?!.*?0) |
Dropped (?!.*?0) |
Discarded (?!.*?0) |
Bad (?!.*?0) |
Suspect (?!.*?No) |
Thresholded (?!.*?0) |
Visibility (?!.*?Yes) |
Thermal.*Status (?!.*?OK) |
HA (?!.*?READY)
} $buffer ]
I have an AWStats running and the reports are built from IIS logfiles.
I have an extra section to view all the actions of the executed perlscripts on the site.
The config looks like this:
ExtraSectionName1="Actions"
ExtraSectionCodeFilter1="200 304"
ExtraSectionCondition1="URL,\/cgi\-bin\/.+\.pl"
ExtraSectionFirstColumnTitle1="Action"
ExtraSectionFirstColumnValues1="QUERY_STRING,action=([a-zA-Z0-9]+)"
ExtraSectionFirstColumnFormat1="%s"
ExtraSectionStatTypes1=HPB
ExtraSectionAddAverageRow1=0
ExtraSectionAddSumRow1=1
MaxNbOfExtra1=20
MinHitExtra1=1
The output looks like this:
Action Pages Hits
foo 1234 1234
bar 5678 5678
But there are some actions with the same name in different perl scripts.
I would need this:
Script Action Pages Hits
foo.pl foo 1234 1234
bar.pl foo 1234 1234
foo.pl bar 5678 5678
bar.pl bar 5678 5678
Does anyone know how to create such a report?
EDIT:
I did some more research and all forum posts I've found say that it is not possible to have two columns in an extra section without hacking in awstats.pl
Now I am trying to put it into one column using URLWITHQUERY to output someting like this:
Action Pages Hits
foo.pl?action=foo 1234 1234
foo.pl?action=bar 1234 1234
bar.pl?action=foo 5678 5678
...
The new problem is that the query has more parameters than action, which are unordered.
I tried this
ExtraSectionFirstColumnValues1="URLWITHQUERY,([a-zA-Z0-9]+\.pl\?).*(action=[a-zA-Z0-9]+)"
but AWStats only gets the value from the first bracket pair and ignores the rest. I think it internally works with $1 provided by the perl regex 'magic'.
Any ideas?
maybe?
ExtraSectionFirstColumnTitle1="Script"
ExtraSectionFirstColumnValues1="URL,\/cgi\-bin\/(.+\.pl)`enter code here`"
ExtraSectionFirstColumnFormat1="%s"
ExtraSectionFirstColumnTitle2="Action"
ExtraSectionFirstColumnValues2="QUERY_STRING,action=([a-zA-Z0-9]+)"
ExtraSectionFirstColumnFormat2="%s"
I've found a solution.
awstats.pl fetches the data for the specified extra sections in line 19664 - 19750
This is my modification:
# Line 19693 - 19701 in awstats.pl (AWStats version 7 Revision 1.971)
elsif ( $rowkeytype eq 'URLWITHQUERY' ) {
if ( "$urlwithnoquery$tokenquery$standalonequery" =~
/$rowkeytypeval/ )
{
$rowkeyval = "$1$2"; # I simply added a $2 for the second capture group
$rowkeyok = 1;
last;
}
}
This will get the first and the second capture group specified in the ExtraSectionFirstColumnValuesX regex.
Example:
ExtraSectionFirstColumnValues1="URLWITHQUERY,([a-zA-Z0-9]+\.pl\?).*(action=[a-zA-Z0-9]+)"
Needless to say that you need to add a $3 $4 $5 ... if you need more groups.