Inelegant Solution with 3 Questions; class, dictionary, AD

This solution seems inelegant because of the reliance on hard-coded dictionary labels to trigger a method’s switch.

  1. Should a child class replace the dictionary?
  2. If so, how would the class property names be called in order to trigger the switch without adding overhead (i.e., addition of PowerShell cmdlets, Where-Object)?
  3. If the dictionary is retained, should I use [SortedDictionary [string, string]] instead?
    1. Properties & elements throughout the parenting cmdlet are arranged a-z by personal principle.

Background

  • [ordered]@{} is avoided for fewer .NET conversions later (custom cmdlet is workflow member).
  • $times, in validation, has appeared to have the correct sorting to continue using $times[n].
  • $times[n] is joined with other elements in a Hash key for a later SQL Upsert (not shown).
  • Our Active Directory has large management variations (national scope: departmentally and regionally managed).
  • Get-ADUser is scoped to only a few service account OUs in the parent custom cmdlet (not shown).

Remarks

  • AD values are typed as [datetime] and [Int64]; and may be null, empty/ws, year -lt 1900, or 0.
  • if/else in foreach($i in $x.Keys) was introduced late—after a raised type conversion error on the $value parameter.
    • This redundancy might be removed in the method in the next iteration.

Applies to

–targeted features only; additional parts removed

using namespace System.Collections;
using namespace System.Collections.Generic;

Enum SystemDateType
{
    AccountLockoutTime = 0;
    Created = 1;
    LastBadPasswordAttempt = 2;
    LastLogonDate = 3;
    Modified = 4;
    PasswordLastSet = 5;
    BadPasswordTime = 16;
    LastLogoff = 17;
    LastSuccessfulInteractiveLogonTime = 18;
    LockOutTime = 19;
}

Class InventoryItem
{
    [datetime] $AccountLockoutTime;
    [datetime] $BadPasswordTime;
    <#  various properties, 28 total arranged a-z #>
    [datetime] $PasswordLastSet;

    [datetime]ConvertToSQLDateTime([string] $value, [string] $key)
    {
        $convert = Switch($value)
        {
            {[string]::IsNullOrWhiteSpace($_) -or $_ -eq 0}{'1900-01-01'; break}
            {[SystemDateType]::$key.value__ -le 5 -and ([datetime]$_).Year -lt 1900}{'1900-01-01'; break}
            {[SystemDateType]::$key.value__ -gt 5}{[datetime]::FromFileTime($_); break}
            default{$_}
        }
        return $convert;
    }
}

Class InventoryCollection {<#... has AddItem() method for instances of InventoryItem #>}
$invenColl = [InventoryCollection]::new();

foreach($u in (Get-ADUser -Filter{ name -like <#[...]#>} <#[other params...]#>))
{
    $results = [InventoryItem]::new();

    $x = [dictionary[string,string]]::new();
    $x['AccountLockoutTime'] = "$($u.AccountLockoutTime)";
    $x['BadPasswordTime'] = "$($u.BadPasswordTime)";
    # [...]
    $x['PasswordLastSet'] = "$($u.PasswordLastSet)";

    $times = [ArrayList]::new();

    foreach($i in $x.Keys)
    {
        if([string]::IsNUllOrEmpty($x[$i]))
        {
            [void]$times.Add($results.ConvertToSQLDateTime(0,$i));
        }
        else
        {
            [void]$times.Add($results.ConvertToSQLDateTime($x[$i],$i));
        }
    }

    $results.AccountLockoutTime = $times[0];
    $results.BadPasswordTime = $times[1];
    # [...]
    $results.PasswordLastSet = $times[9];
    $invenColl.AddItem($results);
}

Where is this code from? It appears to be a mix of C# and Powershell?

It is from a new cmdlet that I’m writing to replace an older one.
PS Version: 5.1.14409.1018

From first glance, there are already Powershell cmdlets that get the information you are getting, like Get-ADUser. Working in Powershell for a long time, I have not had to use C# for almost anything. There are public modules for almost anything, such as SQL Write-SqlTableData for insert\upsert of data. Why is C# needed for the functionality you are providing?

If you choose to continue using C#, ordering isn’t really a functional requirement, it’s typically something for reporting when you want to display data to a user a certain way. If the code works and you are just looking for efficiency, you can either peruse GitHub for C#\Powershell examples or ask directed questions in a C# related forum.

@rob-simmers - thank you for your attention on this topic!

As widespread variations in our Active Directory management prohibit the reliable use of some AD Object properties, these classes construct instances to fit project requirements. The code extends a Get-ADUser result set to meet business needs.

This code is not C#, but represents newer capabilities of PowerShell. I use direct type casting to offset the possible costs of New-Object; and have had the impression that classes can outperform [DataTable] where a lesser object type suffices (that said, our network can be a roller coaster). Elsewhere, there remain cases when a data table object and New-Object are used. I typically call SMO classes for SQL Server actions and items, which integrate well with the custom classes.

Sort Order

I had wondered about the sorting of the dictionary elements as they pass into the method, because that method’s output is stored in an array list, and those items are used as $times[n] to set elements in $results. With this, sort order is crucial to setting the correct value with its property. Not shown in the code piece above: $times[0..9] is called along with other tracked properties when generating a hash key for each instance (by another method); and this expression reduces coding and looping.

Effort

I avoid hard-coded labels (&etc) where I can, but resorted to the dictionary object as it met the basic functional needs: treat Value based on Key. And so, is there was an efficient alternative to it?

Ah, I see. As you may have ascertained, I have not done much with the classes implementation in Powershell. I still see all of that code that is taking results from Get-ADUser, that returns a PSObject, and converting that into a hash table (dictionary) to convert pwdlastset into an epoch time. This can be done with something as simple as this:

$users = Get-ADUser -Filter{ name -like <#[...]#>} | 
         Select *, 
                @{Name='EpochTime';Expression={Get-Date -Date $_.PasswordLastSet -UFormat %s}}

I’m not understanding the complexity. The only reason the PSObject would be converted to a Hash is if I was doing lookups against that, but if you are posting to SQL then my assumption is that you would be doing that in a database.