Searching for String Properties with Powershell

I had a requirement recently to parse a configuration file (let’s just say for documentation purposes) and I needed to retrieve a property/value pair which may or may not be present in a text line. Now, depending on the product we wish to document, we might have a line in the configuration file constructed as follows:

set interface 1/3 -ifAlias DMZ -throughput 0 -bandwidthHigh 0 -bandwidthNormal 0 -intftype "Xen Virtual" -ifnum 1/3

Now, if wanted the “-intfType” value we could split the string using the Powershell .Split() method like so:

PS C:\> $SourceString = 'set interface 1/3 -ifAlias DMZ -throughput 0 -bandwidthHigh 0 -bandwidthNormal 0 -intftype "Xen Virtual" -ifnum 1/3';
$LineComponents = $SourceString.Split();
$LineComponents[12];
"Xen

However, there are two issues here:

  1. What happens if there are additional or missing properties (the index/order will change)?
  2. We were hoping/expecting to see “Xen Virtual” output and not just “Xen.

Here’s a quick function that will solve issue #1 (complete with case insensitivity):

#############################################################################
#.SYNOPSIS
# Get named property from a string.
#
#.DESCRIPTION
# Returns a case-insensitive property from a string, assuming the property is
# named before the actual property value and is separated by a space. For
# example, if the specified SearchString contained
# "-property1 <value1> -property2 <value2>”, searching # for "-Property1"
# would return "<value1>".
#
#.PARAMETER SearchString
# String to search for the specified property in.
#
#.PARAMETER PropertyName
# The property name to search for.
#
#.PARAMETER Default
# If the property is not found return the specified string. This parameter is
# optional and if not specified returns $null by default.
#
#.EXAMPLE
# $propertyValue = Get-StringProperty $StringToSearch "-property1"
#
#.EXAMPLE
# $propertyValue = Get-StringProperty $StringToSearch "-property3" "Not found"
##############################################################################
function Get-StringProperty([string]$SearchString, [string]$PropertyName, [string]$Default = $null)
{
    # Split the $SearchString based on one or more blank spaces
    $stringComponents = $SearchString.Split(' +',[StringSplitOptions]'RemoveEmptyEntries'); 
    for ($i = 0; $i -le $stringComponents.Length; $i++) {
        # The standard Powershell CompareTo method is case-sensitive
        if ([string]::Compare($stringComponents[$i], $PropertyName, $true) -eq 0) {
            # Check that we're not over the array boundary
            if ($i+1 -le $stringComponents.Length) {
                # If you wanted to trim quotation marks you could use this instead:
                #  return $StringComponents[$i+1].Trim('"');
                return $stringComponents[$i+1];
            }
        }
    }
    # If nothing has been found or we're over the array boundary, return the $EmptyString value
    return $Default;
}

$SourceString = 'set interface 1/3 -ifAlias DMZ -throughput 0 -bandwidthHigh 0 -bandwidthNormal 0 -intftype "Xen Virtual" -ifnum 1/3';
Get-StringProperty $SourceString "-intftype"

"Xen

Solving issue #2 is a little more involved as we need find the quoted text, escape it and then restore it when needed. The following Get-StringProperty advanced function will first replace all quoted spaces with “^” and then replace the quotes themselves with “^^”. This permits the split function to work as expected. Finally, everything is put back together just before we need it!

<#
.SYNOPSIS
   Get a named property value from a string.
.DESCRIPTION
   Returns a case-insensitive property from a string, assuming the property is
   named before the actual property value and is separated by a space. For
   example, if the specified SearchString contained "-property1 <value1>
   -property2 <value2>”, searching for "-Property1" would return "<value1>".
.PARAMETER SearchString
   String to search for the specified property name.
.PARAMETER PropertyName
   The property name to search the SearchString for.
.PARAMETER Default
   If the property is not found returns the specified string. This parameter is
   optional and if not specified returns $null (by default) if the property is
   not found.
.EXAMPLE
   Get-StringProperty -SearchString $StringToSearch -PropertyName "-property1"

   This command searches the $StringToSearch variable for the presence of the property
   "-property1" and returns its value, if found. If the property name is not found,
   the default $null will be returned.
.EXAMPLE
   Get-StringProperty $StringToSearch "-property3" "Not found"

   This command searches the $StringToSearch variable for the presence of the property
   "-property3" and returns its value, if found. If the property name is not found,
   the "Not Found" string will be returned.
.NOTES
   Author - Iain Brighton - @iainbrighton, iain.brighton@virtualengine.co.uk
#>
function Get-StringProperty {

    [CmdletBinding(HelpUri = 'https://virtualengine.co.uk/2014/searching-for-string-properties-with-powershell/')]
    [OutputType([String])]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [Alias("Search")]
        [string]
        $SearchString,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        [Alias("Name","Property")]
        [string]
        $PropertyName,

        [Parameter(ValueFromPipelineByPropertyName=$true, Position=2)]
        [AllowNull()]
        [String]
        $Default = $null
    )

    Begin { }

    Process {
        # Locate and replace quotes with '^^' and quoted spaces '^' to aid with parsing, until there are none left
        while ($SearchString.Contains('"')) {
            # Store the right-hand side temporarily, skipping the first quote
            $searchStringRight = $SearchString.Substring($SearchString.IndexOf('"') +1);
            # Extract the quoted text from the original string
            $quotedString = $SearchString.Substring($SearchString.IndexOf('"'), $searchStringRight.IndexOf('"') +2);
            # Replace the quoted text, replacing spaces with '^' and quotes with '^^'
            $SearchString = $SearchString.Replace($quotedString, $quotedString.Replace(" ", "^").Replace('"', "^^"));
        }

        # Split the $SearchString based on one or more blank spaces
        $stringComponents = $SearchString.Split(' +',[StringSplitOptions]'RemoveEmptyEntries'); 
        for ($i = 0; $i -le $stringComponents.Length; $i++) {
            # The standard Powershell CompareTo method is case-sensitive
            if ([string]::Compare($stringComponents[$i], $PropertyName, $True) -eq 0) {
                # Check that we're not over the array boundary
                if ($i+1 -le $stringComponents.Length) {
                    # Restore any escaped quotation marks and spaces
                    # If you wanted to trim quotation marks you could use this instead:
                    #  return $stringComponents[$i+1].Replace("^^", '"').Replace("^", " ").Trim('"');
                    return $stringComponents[$i+1].Replace("^^", '"').Replace("^", " ");
                }
            }
        }
        # If nothing has been found or we're over the array boundary, return the default value
        return $Default;
    }

    End { }
}

$SourceString = 'set interface 1/3 -ifAlias DMZ -throughput 0 -bandwidthHigh 0 -bandwidthNormal 0 -intftype "Xen Virtual" -ifnum 1/3';
Get-StringProperty $SourceString "-intftype"

"Xen Virtual"

That’s much better Smile. Just for good luck I have also created a complimentary Test-StringProperty advanced function that tests whether a property name is present or not. This removes a lot of if ((Get-StringPropertyValue $SearchString “PropertyName”) -ne $null) { Do-Something } calls.

<#
.SYNOPSIS
   Test for a named property value in a string.
.DESCRIPTION
   Tests for the presence of a property value in a string and returns a boolean
   value. For example, if the specified SearchString contained "-property1
   -property2 <value2>”, searching for "-Property1" or "-Property2" would return
   $true, but searching for "-Property3" would return $false
.PARAMETER SearchString
   String to search for the specified property name.
.PARAMETER PropertyName
   The property name to search the SearchString for.
.EXAMPLE
   Test-StringProperty -SearchString $StringToSearch -PropertyName "-property1"

   This command searches the $StringToSearch variable for the presence of the property
   "-property1". If the property name is found it returns $true. If the property name
   is not found, it will return $false.
.NOTES
   Author - Iain Brighton - @iainbrighton, iain.brighton@virtualengine.co.uk
#>
function Test-StringProperty {

    [CmdletBinding(HelpUri = 'https://virtualengine.co.uk/2014/searching-for-string-properties-with-powershell/')]
    [OutputType([bool])]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [Alias("Search")]
        [string]
        $SearchString,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        [Alias("Name","Property")]
        [string]
        $PropertyName
    )

    Begin { }

    Process {
        # Split the $SearchString based on one or more blank spaces
        $stringComponents = $SearchString.Split(' +',[StringSplitOptions]'RemoveEmptyEntries'); 
        for ($i = 0; $i -le $stringComponents.Length; $i++) {
            # The standard Powershell CompareTo method is case-sensitive
            if ([string]::Compare($stringComponents[$i], $PropertyName, $True) -eq 0) { return $true; }
        }
        # If nothing has been found or we're over the array boundary, return the default value
        return $false;
    }

    End { }
}

Some final words of warning:

  • If there are escaped (double) quotes then this function won’t work without some modification.
  • If the $SearchString just so happens to natively contain either “^” and/or “^^” it won’t (currently) work either.
  • If you have any improvements or feedback then please let me know.
  • Please test in your environment before putting any 3rd party or external code into production!

RES IT Store Web Portal Setup

We’ve had some confusion both internally and externally with some customers with the installation of the RES IT Store Web Portal. During the installation you will be prompted as to how you would like the IIS website to be configured:

image

This dialog is a little bit unclear as to what it is actually asking you. If you follow these simple guidelines, I’m sure you’ll be a whole lot clearer as to what is going on:

  • The RES IT Store Web Portal setup will create a new website – no ifs, buts or maybes.
    • It will not install to the default IIS website.
  • If you select the “Yes” option, the new website will be bound to port TCP port 80:
    • The existing default IIS website will also be bound to port 80 and therefore only one will start.
    • You will need to manually change the IIS default website port bindings.
  • If you select the “No” option, the new website can be bound to a specific TCP port:
    • Be careful as it will default to TCP port 80 but you can manually alter it.
    • The wizard will bind the Hostname specified to the new website as an IIS Host Header Name.

Our recommendation is to always select “No”, leave the default port binding and configure the host header value with your load-balanced fully qualified DNS alias, i.e. itstore.virtualengine.co.uk.

Easy!

Windows 8.1 Update KB 2919355 Error 0x8007003

It’s been long, too long since the last post. However, never fear as we have plenty of new updates in the pipeline (pun intended!). This post is a little off topic but one that might help one or two people with the latest Windows 8.1 Update 1.

Whilst installing the latest Windows 8.1 Update 1 (and also KB2894853) on a couple of machines I saw the following error:

Installation Failure: Windows failed to install the following update with error 0x8007003: Update for Windows (KB2919355)

A quick Google landed me here which mentions that you will receive this error if the profiles directory is redirected with a registry key. As we have small SSDs in the office machines, the profile directory is indeed redirected to a bigger, spinning disk. This article is a little confusing (hence the post) as it states:

After putting a copy of UserProfiles on the C: drive, installation finished normally.

To fix the installation error we need to ensure that the Default directory is present in the redirected folder location, not on the C: drive. A quick copy of C:\Users\Default to D:\Users\Default fixed the installation issues with both updates.