Archive | PowerShell RSS feed for this section

Pseudo Singleton Pattern in PowerShell

1 Oct

I have had a few modules that I have developed where I desired to use the Singleton pattern as I didn’t want multiple instances of a particular object running about across scripts. Building full fledge objects in PowerShell is a bit verbose and not the most straightforward process. I’m also not certain you can properly build an honest Singleton class natively in PowerShell. An alternative to building objects natively in PowerShell is to use C# in line and compile the class with Add-Type. Although that is an acceptable solution and benefits from full enforcement of restricting object creation to calling the Instance method, I wanted to stay native to PowerShell. I’ve adopted the following pattern to accomplish this:

function Get-ObjectInstance{
    param()

    if(Get-Variable -Scope Global -Name 'tmpObject' -EA SilentlyContinue){ 
        return (Get-Variable -Scope Global -Name 'tmpObject').Value 
    }

    $Global:tmpObject = New-Object PSObject
    

   return $tmpObject
}

Using a global variable is required if you want the object available across scripts which is helpful if you want to do things like logging. This concept was initially prompted by an issue I was having with a Logging module I had written. I was invoking a Global logging object in each script which was causing problems on multiple runs. I would call:

# Instatiate Log #
if($GLOBAL:Log -eq $null){
    $GLOBAL:Log = New-LogObject "Log Description"
}

On subsequent runs I would get errors of “Can’t add-member… “ something or other and the logging would error. It required me closing the object properly which was difficult to do cleanly and consistently when creating the Global object in each script as the name could change from script to script if you weren’t careful. Instead of making the variable global in each script, I moved that into a module using the “singleton” pattern as described above in the function Get-ObjectInstance . I initially moved to this method to make it easier to close out the global variable so I wouldn’t run into issues on subsequent runs. The side benefit of this change was that I no longer needed to maintain consistency from script to script for the variable name. Using the pseudo singleton pattern, I could now call:

# Instatiate Log #
$Log = Get-LogInstance "Log Description"

This remove the need for checking if the variable had been created in each script, and I could call scripts in any order knowing I would get a valid and the correct instance of the object I wanted.

Removing a Global Variable – Revisited

14 Nov

I had previously written about an issue that I had with closing out a custom PSObject that caused scripts to fail on subsequent runs. After watching a PluralSight video that covered the concept of passing variables by reference and by value, I re-visited my issue. Initially, I was trying to solve the issue by capturing the variable name and using that name to call:

Remove-Variable -Name 'VariableName'

Although Remove-Variable requires a name to remove the variable, I was going about identifying the name of variable the wrong way. Since the $this variable in the custom PSObject is going to reference the same object as the variable I assigned it to in my scripts I could leverage System.Object.ReferenceEquals() method to identify the variables. My new Close() method for my PSLogging module looks like this:

$tmpVars = Get-Variable -Scope Global | Where-Object{
    [System.Object]::ReferenceEquals($this, $_.Value)}
if($tmpVars.GetType().FullName -eq 'System.Management.Automation.PSVariable'){
    Remove-Variable -Scope Global -Name $tmpVars.Name
}else{
    for($i=0;$i -lt $tmpVars.Count;$i++){
        Remove-Variable -Scope Global -Name $tmpVars[$i].Name
    }
}

I can now reliably identify all references to the log variable and remove them successfully. The concepts of By Value and By Reference weren’t new to me, but I didn’t fully grasp them until watching that video.

Removing a Global Variable from the PowerShell environment

26 Jan

UPDATE: It appears that this fix only works when called from the commandline. InvocationPoint.MyCommand.Definition is populated with the script that called it initially. Back to the drawing board for another solution.

Update 11/9/13: Added source code highlighting; typo fixes

Situation:

I had created a custom PowerShell module to use to log activity in other scripts I was writing. The module exposes a function that outputs a custom PSObject to hold the messages. In my scripts, I assigned this PSObject to a global function so it was available to other scripts I might call. At the end of the script, I wanted a method to close or remove the variable the the PSObject was assigned to so that it wouldn’t cause issues on the subsequent runs.

Issue:

The problem I ran into is there isn’t an easy way for removing the variable holding the PSObject. Trying to set $this = $null in the module doesn’t null out the containing object but the $this variable instead. Attempting to remove the variable with the Remove-Variable and $this also failed. My temporary solution was to iterate through all of the NoteProperty’s that were set and $null them out individually. This worked for a while, but required me to close and reopen the ISE between running scripts as the next attempt to run a script caused PowerShell to complain that it couldn’t add to the log variable.

Solution:

I finally tired of having to reopen the ISE and decided to fix my problem properly. The following is my solution:

function Get-VariableName{ 
    param() 
    $tmpRegex = '(?<=$[A-Z,a-z,0-9]*:).*(?=(s=|=))' 
    $this.InvocationPoint.MyCommand.Definition -match $tmpRegex | Out-Null 
    return $Matches[0].Trim() 
}

…

# Close # 
$tmpObject | Add-Member -MemberType ScriptMethod -Name Close ` 
    -Value { 
        param() 
        Remove-Item -Path "Variable:$(Get-VariableName)" 
}

The Get-VariableName uses regex to parse the variable name from the InvocationPoint of $this. The property $this.InvocationPoint.MyCommand.Definition exposes the full command used to create the variable. This was the only place I could find this information exposed in the object itself without adding a property. Now that I had the variable name, I used the Remove-Item function to remove the variable. That is probably the long way of removing instead of just using:

Remove-Variable –Name $(Get-VariableName) –Scope Global

Somehow I overlooked that command when I implemented the fix.

This works for now, but I may need to update the regex at some point to support both global and none global variables as I believe it will fail to capture none global variables. Fortunately I only use this with globally defined variables.