46

With PowerShell being built on top of the .NET framework, can I write my own custom class using PowerShell?

I'm not talking about instantiating .NET classes... that part is plain enough. I want to write my own custom classes using PowerShell scripts. Is it possible? So far my research leads me to say that this isn't possible.

I'm still learning PowerShell, and so far I haven't found an answer on this website, despite a few searches.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
C.J.
  • 15,637
  • 9
  • 61
  • 77

3 Answers3

50

Take a look at the Add-Type cmdlet. It lets you write C# and other code in PowerShell. For example (from the above link), in a PowerShell window,

$source = @"
public class BasicTest
{
    public static int Add(int a, int b)
    {
        return (a + b);
    }

    public int Multiply(int a, int b)
    {
        return (a * b);
    }
}
"@

Add-Type -TypeDefinition $source

[BasicTest]::Add(4, 3)

$basicTestObject = New-Object BasicTest
$basicTestObject.Multiply(5, 2)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ravikanth
  • 24,922
  • 4
  • 60
  • 60
  • 1
    Sorry, guess I misunderstood. I thought that you wanted the functionality of classes but done completely in PS. Yes if you want to write a .net class and use it in PS then Add-Type is the way to go. I have updated my answer to match the example here but purely in PS just in case someone is looking for it. The only difference is that I made the multiply function private and included the supersecret function as an interface to multiply. – EBGreen Jul 27 '11 at 19:29
  • 1
    This answer is outdated as of PowerShell 5.0 which introduces the class keyword. See @Rubanov answer below. – Guillaume CR Oct 01 '15 at 19:24
44

I suspect that the solution that you are looking for is PowerShell Modules. They perform the roles that classes typically perform in other languages. They give you a very simple, yet structured, way to reuse your code.

Here is how to get the functionality of classes in PowerShell using modules. At the command line you could do this:

New-Module -ScriptBlock {function add($a,$b){return $a + $b}; function multiply($a,$b){return $a * $b}; function supersecret($a,$b){return multiply $a $b}; export-modulemember -function add, supersecret}

Then you would be able to:

PS C:\> add 2 4
6
PS C:\> multiply 2 4
The term 'multiply' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:9
+ multiply <<<<  2 4
    + CategoryInfo          : ObjectNotFound: (multiply:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

PS C:\> supersecret 2 4
8

As you can see multiply is private within the module. More traditionally you would instantiate an object that is an instance of the module. That is done via the -AsCustomObject parameter:

$m = New-Module -ScriptBlock {function add($a,$b){return $a + $b}; function multiply($a,$b){return $a * $b}; function supersecret($a,$b){return multiply $a $b}; export-modulemember -function add, supersecret} -AsCustomObject

Then you could:

PS C:\> $m.add(2,4)
6
PS C:\> $m.multiply(2,4)
Method invocation failed because [System.Management.Automation.PSCustomObject] doesn't contain a method named 'multiply'.
At line:1 char:12
+ $m.multiply <<<< (2,4)
    + CategoryInfo          : InvalidOperation: (multiply:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

PS C:\> $m.supersecret(2,4)

8

This all demonstrates the use of dynamic modules meaning nothing is stored to disk for reuse. It is fine for very simple functionality. If you want to actually be able to read the code and reuse it in future sessions or scripts, however, you would want to store it in a .psm1 file and then store that file in a folder with the same name (minus the extension) as the file. Then you can import the module into your session at the command line or into another script.

As an example of this, let's say I took this code:

function Add{
    param(
            $a,
            $b
         )
    return $a + $b
}

function Multiply{
    param(
            $a,
            $b
         )
    return $a + $b
}

function SuperSecret{
    param(
            $a,
            $b
         )
    return Multiply $a $b
}
Export-ModuleMember -Function Add, SuperSecret

And saved it to a file called TestModule.psm1 in the folder: C:\Windows\System32\WindowsPowerShell\v1.0\Modules\TestModule

The Modules folder in the PowerShell install folder is a magic folder and any modules stored there are visible to the Import-Module cmdlet without having to specify a path. Now if we run Get-Module -List at the command line we see:

ModuleType Name                      ExportedCommands
---------- ----                      ----------------
Script     DotNet                    {}
Manifest   FileSystem                {Get-FreeDiskSpace, New-Zip, Resolve-ShortcutFile, Mount-SpecialFolder...}
Manifest   IsePack                   {Push-CurrentFileLocation, Select-CurrentTextAsVariable, ConvertTo-Short...
Manifest   PowerShellPack            {New-ByteAnimationUsingKeyFrames, New-TiffBitmapEncoder, New-Viewbox, Ne...
Manifest   PSCodeGen                 {New-Enum, New-ScriptCmdlet, New-PInvoke}
Manifest   PSImageTools              {Add-CropFilter, Add-RotateFlipFilter, Add-OverlayFilter, Set-ImageFilte...
Manifest   PSRss                     {Read-Article, New-Feed, Remove-Article, Remove-Feed...}
Manifest   PSSystemTools             {Test-32Bit, Get-USB, Get-OSVersion, Get-MultiTouchMaximum...}
Manifest   PSUserTools               {Start-ProcessAsAdministrator, Get-CurrentUser, Test-IsAdministrator, Ge...
Manifest   TaskScheduler             {Remove-Task, Get-ScheduledTask, Stop-Task, Add-TaskTrigger...}
Manifest   WPK                       {Get-DependencyProperty, New-ModelVisual3D, New-DiscreteVector3DKeyFrame...
Manifest   AppLocker                 {}
Manifest   BitsTransfer              {}
Manifest   PSDiagnostics             {}
Script     **TestModule**            {}
Manifest   TroubleshootingPack       {}
Manifest   Citrix.XenApp.Commands... {}

We can see that our module is ready to import. We can import it into the session and use it in the raw using:

Import-Module TestModule

Or once again we can instantiate an object:

$m = Import-Module TestModule -AsCustomObject
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
EBGreen
  • 36,735
  • 12
  • 65
  • 85
  • So it looks like the answer is no. Even in modules I don't see any documentation about writing custom classes. I guess I have to use C# for writing custom classes. – C.J. Jul 27 '11 at 17:51
  • I guess I don't understand what you mean by classes? What do you want the class to do? – EBGreen Jul 27 '11 at 17:52
  • I can write a class in C# that has public and private member variables and methods. So far it doesn't look like powershell does that. – C.J. Jul 27 '11 at 17:57
  • 6
    From the link: "Modules can be used to package and distribute a cohesive library of functions for doing common tasks. Typically, the names of these functions share one or more nouns that reflect the common task that they are used for. These functions can also be similar to .NET Framework classes in that ***they can have public and private members***." – EBGreen Jul 27 '11 at 18:05
24

You can use the class keyword that was introduced in PowerShell 5.0

Here is an example by Trevor Sullivan. (Archived here.)

##################################################
####### WMF 5.0 November 2014 Preview ###########
##################################################
class Beer {
    # Property: Holds the current size of the beer.
    [Uint32] $Size;
    # Property: Holds the name of the beer's owner.
    [String] $Name;

    # Constructor: Creates a new Beer object, with the specified
    #              size and name / owner.
    Beer([UInt32] $NewSize, [String] $NewName) {
        # Set the Beer size
        $this.Size = $NewSize;
        # Set the Beer name
        $this.Name = $NewName;
    }

    # Method: Drink the specified amount of beer.
    # Parameter: $Amount = The amount of beer to drink, as an 
    #            unsigned 32-bit integer.
    [void] Drink([UInt32] $Amount) {
        try {
            $this.Size = $this.Size - $Amount;
        }
        catch {
            Write-Warning -Message 'You tried to drink more beer than was available!';
        }
    }

    # Method: BreakGlass resets the beer size to 0.
    [void] BreakGlass() {
        Write-Warning -Message 'The beer glass has been broken. Resetting size to 0.';
        $this.Size = 0;
    }
}

This works on Windows 10 Pro.

Test drive it like this:

# Create a new 33 centilitre beer, named 'Chimay'
$chimay = [Beer]::new(33, 'Chimay');

$chimay.Drink(10)
$chimay.Drink(10)

# Need more beer!
$chimay.Drink(200)

$chimay.BreakGlass()
MonkeyDreamzzz
  • 3,978
  • 1
  • 39
  • 36
  • 1
    I found ([here](https://msdn.microsoft.com/en-us/powershell/wmf/5.0/class_newtype#end-to-end-example)) you can initialize by casting a hash table: `$chimay = [Beer]@{ Size=33; Name='Chimay'}`. Kind of like C#'s object initializer syntax. Throws error if constructor has parameters, though. – Vimes Feb 02 '17 at 20:37