Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Answering the PowerShell Registered User Challenge

Posted on November 16, 2020November 16, 2020

A few weeks ago, an Iron Scripter PowerShell challenge was issued. This was a beginner to intermediate level challenge to get and set the registered user and/or organization values. These challenges, and solutions such as mine, aren't intended to production-ready tools. Instead, you should use them as learning vehicles to advance your PowerShell scripting skills. The concepts and techniques are more important than the actual result. Feel free to stop reading and try your hand at the challenge.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Read the Registry

The information for registered user and organization is stored in the registry. You can use Get-ItemProperty.

The cmdlet returns extra information, so you might want to be more selective.

Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name registered* |
Select-Object -property Registered*

Another option is to use Get-ItemPropertyValue to retrieve the value alone.

Get-ItemPropertyValue -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name registeredowner,registeredorganization

With these ideas in mind, here is a simple function to get the necessary information from the local computer.

Function Get-RegisteredUserSimple {
    Param()

    $path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"
    Get-ItemProperty -Path $path | 
    Select-Object -Property RegisteredOwner,RegisteredOrganization,
    @{Name="Computername";Expression={$env:computername}}
}

Sometimes, the use of Select-Object can get in the way of clarity. That's why I tend to define custom objects like this:

Function Get-RegisteredUserBasic {
    [cmdletbinding()]
    Param()

    $path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"
    $reg = Get-ItemProperty -Path $path

    [pscustomobject]@{
        RegisteredUser         = $reg.RegisteredOwner
        RegisteredOrganization = $reg.RegisteredOrganization
        Computername           = $env:COMPUTERNAME
    }
}

The output will be the same.

Set the Registry

What about setting new values? For that task we can use Set-ItemProperty. I'm trusting you'll read full help and examples for all of the commands I'm using. Here's my relatively simple function to set the owner and/or organization values.

Function Set-RegisteredUserBasic {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [alias("user")]
        [ValidateNotNullOrEmpty()]
        [string]$RegisteredUser,

        [Parameter()]
        [alias("org")]
        [ValidateNotNullOrEmpty()]
        [string]$RegisteredOrganization,

        [switch]$Passthru
    )

    #registry path
    $path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"

    #a flag variable to indicate if a change was made
    $set = $False

    #Only set property if something was entered
    if ($RegisteredUser) {
        Write-Verbose "Setting Registered Owner to $RegisteredUser"
        Set-ItemProperty -Path $path -Name "RegisteredOwner" -Value $RegisteredUser
        $set = $True
    }

    #Only set property if something was entered
    if ($RegisteredOrganization) {
        Write-Verbose "Setting Registered Organization to $RegisteredOrganization"
        Set-ItemProperty -Path $path -Name "RegisteredOrganization" -Value $RegisteredOrganization
        $set = $True
    }

    #passthru if something was set
    if ($set -AND $passthru) {

        $reg = Get-ItemProperty $path
        [pscustomobject]@{
            RegisteredUser         = $reg.RegisteredOwner
            RegisteredOrganization = $reg.RegisteredOrganization
            Computername           = $env:COMPUTERNAME
        }
    } #if passthru
}

I've included a very intermediate-level features such as support for -WhatIf, some parameter validation and parameter aliases. Because I configured cmdletbinding to use "SupportsShouldProcess", I'll automatically get the WhatIf and Confirm parameters. Even better, any command that I call will automatically consume the parameter values. In my function, Set-ItemProperty supports -WhatIf. But I don't have to do anything special.

My code is using a -Passthru switch parameter to get value, if specified.

I don't have any real error-handling in this function. In order to modify the this part of the registry you need to be running PowerShell as administrator. If I wasn't, Set-ItemProperty would throw an exception, which I would be fine with. One thing I could do is require administrator access.

The functions have to live in a .ps1 file and then be dot-sourced. Let's leave modules out of the discussion. At the top of the .ps1 file I can add these require statements:

#requires -version 5.1
#requires -RunAsAdministrator

When the user dot sources the file, if they are not running as Administrator, PowerShell will throw an exception.

Taking the Next Step

My basic functions work fine when working with the localhost. And I think they would have met the basic requirements of the challenge. But the challenge had extra features. Let's look at those.

When using the registry provider as I am, this only works on the local computer. To access the registry remotely, you can use PowerShell remoting.

 Invoke-Command -scriptblock {
 Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name registered*
 } -computername win10 -credential $artd | Select-Object -property Registered*,PSComputername

I can take this basic idea and turn it into an advanced PowerShell function. By the way, I always start with a command-line solution like this first. Once I have it working, then I can begin building a function around it. If you are new to PowerShell I recommend this approach.

Building a Remoting Function

Here's my get function.

Function Get-RegisteredUser {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, ValueFromPipeline)]
        [alias("cn")]
        [string[]]$Computername = $env:COMPUTERNAME,
        [pscredential]$Credential,
        [int32]$ThrottleLimit,
        [switch]$UseSSL
    )

    Begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        #a scriptblock to run remotely
        $sb = {
            $path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"
            $reg = Get-ItemProperty -Path $path

            [pscustomobject]@{
                PSTypeName             = "PSRegisteredUser"
                RegisteredUser         = $reg.RegisteredOwner
                RegisteredOrganization = $reg.RegisteredOrganization
                Computername           = $env:COMPUTERNAME
            }
        }

        $PSBoundParameters.Add("Scriptblock", $sb)
        $PSBoundParameters.Add("HideComputername", $True)
    }
    Process {
        #add the default computername if nothing was specified
        if (-NOT $PSBoundParameters.ContainsKey("computername")) {
            Write-Verbose "Querying localhost"
            $PSBoundParameters.Computername = $Computername
        }
        Invoke-Command @PSBoundParameters | ForEach-Object {
            #create a custom object
            [pscustomobject]@{
                PSTypeName   = "psRegisteredUser"
                Computername = $_.Computername
                User         = $_.RegisteredUser
                Organization = $_.RegisteredOrganization
                Date         = (Get-Date)
            }
        }
    } #process
    End {
        Write-Verbose "Ending $($MyInvocation.MyCommand)"
    }
}

I've moved all of the registry reading code inside a scriptblock which I'll run with Invoke-Command. The function parameters and the same as Invoke-Command so I can splat the built-in $PSBoundParameters hashtable to the command. Although, you'll see that I'm tweaking it a bit to add parameters.

$PSBoundParameters.Add("Scriptblock", $sb)
$PSBoundParameters.Add("HideComputername", $True)

When the command runs remotely, it sends a serialized object back to me. I'm creating a custom object for each result.

 Invoke-Command @PSBoundParameters | ForEach-Object {
            #create a custom object
            [pscustomobject]@{
                PSTypeName   = "psRegisteredUser"
                Computername = $_.Computername
                User         = $_.RegisteredUser
                Organization = $_.RegisteredOrganization
                Date         = (Get-Date)
            }
        }

Adding Formatting

You'll notice that I defined a typename for the custom object. This is so that I can create a custom format file.

Get-RegisteredUser | 
New-PSFormatXML -path c:\scripts\registered.format.ps1xml -GroupBy Computername -Properties User,Organization,Date

New-PSFormatXML is from the PSScriptTools module. I can edit the xml file to meet my needs. In my case, I set specific column widths and formatted the Date value.

<?xml version="1.0" encoding="UTF-8"?>
<!--
Format type data generated 11/16/2020 11:32:51 by PROSPERO\Jeff

This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.
https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 11/16/2020 11:32:51 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>psRegisteredUser</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <!--
            You can also use a scriptblock to define a custom property name.
            You must have a Label tag.
            <ScriptBlock>$_.machinename.toUpper()</ScriptBlock>
            <Label>Computername</Label>

            Use <Label> to set the displayed value.
-->
        <PropertyName>Computername</PropertyName>
        <Label>Computername</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize />-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>User</Label>
            <Width>18</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Organization</Label>
            <Width>20</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Date</Label>
            <Width>10</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <!--
            By default the entries use property names, but you can replace them with scriptblocks.
            <ScriptBlock>$_.foo /1mb -as [int]</ScriptBlock>
-->
              <TableColumnItem>
                <PropertyName>User</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Organization</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>$_.Date.ToShortDateString()</ScriptBlock>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

I then import the file into my session.

Update-FormatData C:\scripts\registered.format.ps1xml

Now, when I run the function I get better formatted results.

Setting Remote Registry Values

I'll take a similar approach to setting the registry values remotely. I have to have admin access to connect remotely so I don't have to worry about not having access. My set function uses the same concepts and techniques as the get function.

Function Set-RegisteredUser {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [alias("user")]
        [ValidateNotNullOrEmpty()]
        [string]$RegisteredUser,

        [Parameter()]
        [alias("org")]
        [ValidateNotNullOrEmpty()]
        [string]$RegisteredOrganization,

        [Parameter(ValueFromPipeline)]
        [alias("cn")]
        [string[]]$Computername = $env:COMPUTERNAME,

        [pscredential]$Credential,

        [switch]$Passthru
    )

    Begin {
        Write-Verbose "Starting $($myinvocation.MyCommand)"

        $sb = {
            [cmdletbinding()]
            <#
            PowerShell doesn't serialize a switch type very well so I'll
            make passthru a boolean. Although in this situation, instead of
            passing parameters I could have referenced the variables from
            the local host as I'm doing with -Verbose and -WhatIf
            #>
            Param(
                [string]$RegisteredUser,
                [string]$RegisteredOrganization,
                [bool]$Passthru
            )

            $VerbosePreference = $using:verbosepreference
            $WhatIfPreference = $using:WhatifPreference

            #registry path
            $path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"
            Write-Verbose "[$($env:COMPUTERNAME)] Using registry path $path"
            #a flag variable to indicate if a change was made
            $set = $False

            #Only set property if something was entered
            if ($RegisteredUser) {
                Write-Verbose "[$($env:COMPUTERNAME)] Setting Registered Owner to $RegisteredUser"
                #define my own -WhatIf
                if ($pscmdlet.ShouldProcess($env:COMPUTERNAME, "Set registered user to $RegisteredUser")) {
                    Set-ItemProperty -Path $path -Name "RegisteredOwner" -Value $RegisteredUser
                    $set = $True
                }
            }

            #Only set property if something was entered
            if ($RegisteredOrganization) {
                Write-Verbose "[$($env:COMPUTERNAME)] Setting Registered Organization to $RegisteredOrganization"
                if ($pscmdlet.ShouldProcess($env:COMPUTERNAME, "Set registered organization to $RegisteredOrganization")) {
                    Set-ItemProperty -Path $path -Name "RegisteredOrganization" -Value $RegisteredOrganization
                    $set = $True
                }
            }

            #passthru if something was set
            if ($set -AND $passthru) {
                $reg = Get-ItemProperty $path
                [pscustomobject]@{
                    PSTypeName   = "PSRegisteredUser"
                    Computername = $env:COMPUTERNAME
                    User         = $reg.RegisteredOwner
                    Organization = $reg.RegisteredOrganization
                    Date         = Get-Date
                }
            } #if passthru

        } #close scriptblock

        $icmParams = @{
            Scriptblock  = $sb
            ArgumentList = @($RegisteredUser, $RegisteredOrganization, ($Passthru -as [bool]))
            HideComputername = $True
        }
        if ($Credential) {
            Write-Verbose "Using credential for $($credential.username)"
            $icmParams.Add("Credential", $credential)
        }

    } #begin
    Process {
        $icmParams.Computername = $Computername
        Invoke-Command @icmParams | Select-Object -Property * -ExcludeProperty RunspaceID
        #You could also create the custom object on this end as I did with Get-RegisteredUser
    } #process

    End {
        Write-Verbose "Ending $($myinvocation.MyCommand)"
    } #end
}

There are a few things I want to point out. Remember, the scriptblock is running remotely but I need to pass values from my local session. My code is demonstrating a few techniques.

First, the scriptblock is defined with parameters.

$sb = {
    [cmdletbinding()]
    <#
    PowerShell doesn't serialize a switch type very well so I'll
    make passthru a boolean. Although in this situation, instead of
    passing parameters I could have referenced the variables from
    the local host as I'm doing with -Verbose and -WhatIf
    #>
    Param(
        [string]$RegisteredUser,
        [string]$RegisteredOrganization,
        [bool]$Passthru
    )

Later in my code I'll pass the local values to the scriptblock.

$icmParams = @{
  Scriptblock  = $sb
  ArgumentList = @($RegisteredUser, $RegisteredOrganization, ($Passthru -as [bool]))
  HideComputername = $True
}

The other approach is to employ the $using: prefix. This is how I am setting -Verbose and -Whatif in the remote scriptblock.

$VerbosePreference = $using:verbosepreference
$WhatIfPreference = $using:WhatifPreference

In essence, I'm setting the remote preference variables to use the local values.

In my function, instead of passing WhatIf to Set-ItemProperty, I'm defining my own WhatIf code to provide better information.

if ($pscmdlet.ShouldProcess($env:COMPUTERNAME, "Set registered user to $RegisteredUser")) {
    Set-ItemProperty -Path $path -Name "RegisteredOwner" -Value $RegisteredUser
    $set = $True
}

I could follow the same process as with my get function and define a true custom object that could use the same formatting file. I'll leave that exercise for you.

Summary

I hope you'll try these functions out for yourself. Read the cmdlet help so that you understand why the code works. If you have any questions, please leave a comment. And I hope you'll keep an eye out for other Iron Scripter Challenges and try your hand.

By the way, if you are looking for other ways to test your PowerShell skills, especially if you are a beginner, you might want to grab a copy of The PowerShell Practice Primer.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

1 thought on “Answering the PowerShell Registered User Challenge”

  1. Pingback: ICYMI: PowerShell Week of 20-November-2020 – 247 TECH

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d