Category: PowerShell

  • PowerShell Regex Matching

    I was working on a project where I needed to understand the naming convention for the servers. Since they had been made by several teams over several years, there was no convention. It was a giant pain in the ass.

    I wrote a function that would accept the server name and then try to parse it out. While there was not a strong standard, there was a few soft standards I could guess at. It took a while to figure out but PowerShell -match returns an array, if you use regex groups. All I needed was a few regex patterns and then I could start to decode these server names!

    The servers were in different data centers, so some had DEN, ’cause they were in Denver data center (see?). But some were in CINC (with four letters, not even keeping three letters) as they were in the Cincinnati data center. The next few characters in the name gave some hint as to purpose; WEB, or SQL or something more obscure like OTOPS, again with varying number of letters. Last, the server could have a number, 01, 02, etc. or a letter, A, B, C if it was part of a set. But then we had a few that were part of 01 set but there were several of those so you got 01A, 01B, 01C.

    There are several great regex tools on-line to show you how your pattern is working and what the rules are. For the server names I wound up with:

    (?<datacenter>den|cinc)(?<role>\w+)(?<countNum>\d{2})(?<countLet>[a-d])

    PowerShell isn’t case sensitive, so we’ll ignore those differences. Also, I’m using named groups, those are defined with ?<datacenter> where the name of the group is data center. Then everything that matches within the ( ) for that group winds up in $Matches.datacenter. $Matches is a built-in variable and can be referenced by index number if not using named groups.

    switch -regex ($serverName){
      "(?<datacenter>den|cinc)(?<role>\w+)(?<countNum>\d{2})(?<countLet>[a-f])" {
        $datacenter = $Matches.datacenter
        $role       = $Matches.role
        $countNum   = $Matches.countNum
        $countLet   = $Matches.countLet
        break
      }
      "(?<datacenter>den|cinc)(?<role>\w+)(?<countNum>\d{2})" {
        $datacenter = $Matches.datacenter
        $role       = $Matches.role
        $countNum   = $Matches.countNum
        $countLet   = "None"
        break
      }
    }

    This is what I wound up with; the switch takes the name of the server passed to my function, checks it against a few different regex patterns, and where there is a match for all groups of the pattern, executes the code block.

    In the code block I’m pulling the named group and assigning to my values to spit out at the end of the function. Additionally, you see the second pattern is for when there are only numbers in the name. In the running code I had a few more patterns to match all the options used when creating servers. Note the break in the script block, that’s because the switch will keep matching patterns all the way down the statement! The second pattern will give me “None” for my count letter value and not an error or something unexpected.

    I didn’t figure all this out on my own, Kevin’s article on Regex showed me the way. You can get the details on named groups here.

  • parse array for valid usernames

    I was helping a colleague with a list of email addresses and trying to get a valid username out of it. He is going to get a list of emails every week so writing a script to parse it out for him seemed the best idea. This is only part of the process; there are steps to actually create the account if not already existing and more. So this is mainly an example of text parsing to get the info you want based on a few simple rules.

    clear
    $emails = "[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]", "Perumel.Aranin.Senthenekrishnin"
    $usernames = @()
    
    function Get-Username() {
    <#
    .SYNOPSIS
     takes a given email address and makes a username based on specific rules
    .DESCRIPTION
     Converts a given email address to a username. 
     The username cannot be more than 20 char long, if the first and last name combined are too long only first initial is used
     If the email has a number in it, it is retained in the new username
    .PARAMETER
     -email: the email to work with
     -userName: if the email is not parsable, a username can be given to use instead (or in addition to)
    .EXAMPLE
     Get-Username -email "[email protected]" -userName "jchinkes"
    .NOTES
    #>
     Param(
     [Parameter(Mandatory=$True)][string]$email,
     [string]$userName
     )
     $fname = $null
     $lname = $null
     $nameNum = $null
     try {
     #split the email (do we need a test for valid email address?)
     $splitString1 = $email.split('@')
     #take the username of email and split on dot
     $splitString2 = $splitString1[0].split('.')
     
     if($splitString2.Count -le 1){
     #error checking if there is no first or last name
     #is there a better test?
     #use $userName here somehow
     }
     
     if($splitString2.Count -ge 3){
     #more than three items in the array
     if($splitString2[2] -match "^[\d\.]+$"){
     #the third item is a number
     $nameNum = $splitString2[2]
     $lname = $splitString2[1]
     }
     else {
     #third item is a name
     $lname = $splitString2[2]
     }
     }
     else {
     #two part user name
     $lname = $splitString2[1]
     }
     $fname = $splitString2[0]
     
     if($nameNum -ne $null){
     #eval name to see if needs trimming
     if ($fname.get_Length() + $lname.get_Length() -gt 18){
     #trim fname and keep lname
     $fname = $fname.Substring(0,1)
     if ($lname.get_Length() -gt 16){
     #need to trim lname too!
     $lname = $lname.Substring(0,16)
     }
     }
     return "$fname.$lname.$nameNum"
     }
     else {
     if ($fname.get_Length() + $lname.get_Length() -gt 20){
     #trim fname and keep lname
     $fname = $fname.Substring(0,1)
     if ($lname.get_Length() -gt 18){
     #need to trim lname too!
     $lname = $lname.Substring(0,18)
     }
     }
     return "$fname.$lname"
     }
     }
     catch {
     "There was an error- $($_.Exception.Message)"
     }
    }
    
    
    foreach($email in $emails){
     $usernames += Get-Username -email $email
    }
    
    $usernames
  • Make Link List with PowerShell

    A script to get a list of links off a given page. I found these two examples to start from: http://normansolutions.co.uk/post/using-powershell-to-check-all-pages-in-websitehttps://www.petri.com/testing-uris-urls-powershell.

    clear
    function get-URLlist($testPage, [int]$linkDepth){
        #set the counter
        [int]$i = 0     #loop thru the list of links, going to sub-pages until the counter equals the given link depth
        while($linkDepth -gt $i) {
            foreach($link in $testPage.Links) {
                #only look at links on the same site
                if($link.href.ToString() -notlike "http*") {
                    #if not already in the array, add it
                    if(!($sitemap.ContainsKey($link.innerText))){
                        $sitemap.add($link.innerText, $url + "/" + $link.href)
                        $testPage = Invoke-WebRequest "http://$url"
                        #check out the sub-pages one less level than the given link depth
                        $sitemap = get-URLlist -testPage $testPage -linkDepth $($linkDepth-1)
                }
            }
        }
        $i++
    }
    return $sitemap
    } try{
        #set your domain and the array to hold the links
        $url = "www.domain.com"
        $sitemap = @{}     #read the page
        $testPage = Invoke-WebRequest "http://$url"
        #get all the links into the array
        $sitemap = get-URLlist -testPage $testPage -linkDepth 5
        $sitemap
    }
    catch{
        "There was an error- `r`n$($_.Exception.Message)"
    }

    I wrote this with an understanding of what the links look like on this page, so the function formats them with the given URL.