Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
April 4, 2023

Building Class-Based PowerShell Modules

Over the last several weeks, we’ve been exploring how to script with a PowerShell class. The last step is to take everything we’ve covered and wrap it up in a PowerShell module. As you’ll see, there are limitations in using a PowerShell class with a module. But, it is also easier to do more with a class in a module.

Here’s are script files that contains everything we’ve created up to now.

#requires -version 5.1
#requires -RunAsAdministrator

#Get-PSFolderInfo3.ps1

#region enum and class definition

enum FolderInfoTag {
    Documents
    PowerShell
    Media
    Temporary
    SharedData
    Public
    Private
}
class psFolderInfo {
    #region properties
    [String]$Name
    [String]$FullName
    [DateTime]$LastWriteTime
    [TimeSpan]$ModifiedAge
    [Int32]$Files
    [Double]$AverageSize
    [Double]$TotalSize
    [Boolean]$IsEmpty
    [FolderInfoTag[]]$Tag
    hidden [String]$Computername = [System.Environment]::MachineName
    hidden [DateTime]$ReportDate = (Get-Date)
    hidden [String]$LinkTarget
    #endregion

    #region methods
    hidden [void]Initialize([String]$FullName) {
        #move error handling to external code
        $folder = Get-Item -Path $FullName
        $stat = Get-ChildItem -Path $FullName -File -Recurse | Measure-Object -Property length -Sum -Average
        #assign property values to $this
        $this.FullName = $FullName
        $this.Name = $folder.Name
        $this.LastWriteTime = $folder.LastWriteTime
        $this.Files = $stat.Count
        $this.AverageSize = $stat.Average
        $this.TotalSize = $stat.Sum
        $this.IsEmpty = $stat.Count -eq 0
        #update the hidden property
        $this.LinkTarget = $folder.Target
    }

    [void]Refresh() {
        $this.Initialize($this.FullName)
        #update the hidden report date property
        $this.ReportDate = Get-Date
    }

    [System.IO.FileInfo]GetNewest+File() {
        $r = Get-ChildItem -Path $this.FullName -File -Recurse |
        Sort-Object -Property LastWriteTime -Descending |
        Select-Object -First 1
        #must use return key work
        return $r
    }

    [System.IO.FileInfo]GetLargestFile() {
        $r = Get-ChildItem -Path $this.FullName -File -Recurse |
        Sort-Object -Property Length -Descending |
        Select-Object -First 1
        #must use return key work
        return $r
    }
    #endregion

    #region constructors
    psFolderInfo ([String]$FullName) {
        $this.Initialize($FullName)
    } #close constructor
    #endregion

} #close class
#endregion

#region functions

Function Get-PSFolderInfo {
    [cmdletbinding()]
    [OutputType('psFolderInfo')]
    [alias('gpfi')]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            HelpMessage = 'Specify a file system folder'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            if (Test-Path -Path $_) {
                $True
            }
            Else {
                Throw "Failed to verify the path $_"
            }
        })]
        [ValidateScript({
            if ((Get-Item $_).GetType().Name -eq 'DirectoryInfo') {
                $True
            }
            else {
                Throw 'You must specify a folder path.'
            }
        })]
        [ValidateScript({
            if ((Get-Item $_).PSProvider.name -eq 'FileSystem') {
                $True
            }
            else {
                Throw 'You must specify a valid file system path.'
            }
        })]
        [alias('FullName')]
        [String]$Path = '.',

        [ValidateSet("Documents", "PowerShell", "Media", "Temporary", "SharedData", "Public", "Private")]
        [FolderInfoTag[]]$Tag
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Running under PowerShell version $($PSVersionTable.PSVersion)"
    } #begin

    Process {
        #convert path to a full file system path
        $cPath = Convert-Path -Path $Path
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $cPath"
        $obj = New-Object -TypeName psFolderInfo -ArgumentList $cPath
        if ($Tag) {
            Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Adding tag(s) $($tag -join ', ')"
            $obj.Tag = $Tag
        }
        #write result to the pipeline
        $obj
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end

} #close Get-PSFolderInfo

Function  Set-PSFolderInfo {
    [cmdletbinding()]
    [OutputType('psFolderInfo')]
    [alias('spfi')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            HelpMessage = "Specify a psFolderInfo object"
        )]
        [ValidateNotNullOrEmpty()]
        [PSFolderInfo]$InputObject,
        [ValidateSet("Documents", "PowerShell", "Media", "Temporary", "SharedData", "Public", "Private")]
        [FolderInfoTag[]]$Tag
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Running under PowerShell version $($PSVersionTable.PSVersion)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $($InputObject.FullName)"
        if ($Tag) {
            Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Adding tag(s) $($tag -join ', ')"
            $InputObject.Tag = $Tag
        }
        #refresh the object
        $InputObject.Refresh()
        #write result to the pipeline
        $InputObject
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end

} #close  Set-PSFolderInfo
#endregion

#region extend the type
#add an alias property
Update-TypeData -TypeName psFolderInfo -MemberType AliasProperty -MemberName Size -Value TotalSize -Force

#move ModifiedAge to an external script property so it is always updated
Update-TypeData -TypeName psFolderInfo -MemberType ScriptProperty -MemberName ModifiedAge -Value {
    New-TimeSpan -Start $this.LastWriteTime -End (Get-Date)
} -Force
#endregion
#requires -version 5.1

class psFolderReport {
    [string]$Path
    [string]$Name
    [System.IO.FileInfo]$LargestFile
    [System.IO.FileInfo]$OldestFile
    [System.IO.FileInfo]$NewestFile
    hidden [string]$Computername = [Environment]::MachineName
    hidden [DateTime]$ReportDate = (Get-Date)

    psFolderReport([psFolderInfo]$PSFolderInfo) {
        $this.Path = $PSFolderInfo.FullName
        $this.name = $PSFolderInfo.Name
        $this.LargestFile = $PSFolderInfo.GetLargestFile()
        $this.NewestFile = $PSFolderInfo.GetNewestFile()
        $this.OldestFile = $PSFolderInfo.GetOldestFile()
    }
}

Function Get-PSFolderReport {
    [cmdletbinding()]
    [OutputType('psFolderReport')]
    [alias('none')]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            HelpMessage = "Specify a PSFolderInfo object"
        )]
        [ValidateNotNullOrEmpty()]
        [PSFolderInfo]$InputObject
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Running under PowerShell version $($PSVersionTable.PSVersion)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $($InputObject.name)"
        #This will fail because of scoping :New-Object -typename PSFolderReport -ArgumentList $InputObject
        [PSFolderReport]::new($InputObject)
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end

} #close Get-PSFolderReport
Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.