Powershell Class Requests

This topic contains 3 replies, has 4 voices, and was last updated by Profile photo of Jeremy Murrah Jeremy Murrah 9 months ago.

  • Author
    Posts
  • #36061
    Profile photo of Steven Bogar
    Steven Bogar
    Participant

    It does not appear that I can use Parameter validation inside a class.

    When I try to add parameter validation it throws a syntax error:
    Attribute 'ValidateSet' is not valid on this declaration. It is valid on 'Property, Field' declarations only.

    It also does not seem to like using the [System.Enum]::GetNames() function (which returns the same type expected by the ValidateSet attribute), yet demands that 'Attribute argument must be a constant' when you do something like:


    enum LogProviders {
    Uninitialized = 0
    TextFile = 1
    EventLog = 2
    Database = 3
    }
    MyClassConstructor([ValidateSet($([System.Enum]::GetNames([LogProviders])))][LogProviders]$p, [string]$cs)

    I would also like to be able to reference a class from a different ps1 or psm1 file to make sharing code between projects easier.
    Ultimately what I would like to see is that adding a using 'myclass.psm1' to the top of the class would have the same effect as:

    CompanyService.ps1 (example only)

    class Company {
    [string]$CompanyName

    Company([string]$name) {
    $this.CompanyName = $name;
    }
    }

    $syncHash.CompanyService = [CompanyService]::new($syncHash.CoName);
    $syncHash.Error = $Error;

    MyScript.ps1

    [string]$CoServPath = "$PSScriptRoot\CompanyService.ps1";
    $syncHash = [hashtable]::Synchronized(@{});
    $syncHash.CompanyService = $null;

    $rsData = [RunspaceFactory]::CreateRunspace();
    $rsData.Name = $rsName;
    $rsData.Open();

    # set the hash table that will be passed back and forth between threads
    $rsData.SessionStateProxy.SetVariable("syncHash",$syncHash);

    # create the new powershell thread and give it our runspace
    $psComp = [PowerShell]::Create();
    $psComp.Runspace = $rsData;

    # add the script that loads the company service
    $psComp.AddScript($CoServPath) | Out-Null;

    #execute the company service script in a separate thread
    $result = $psComp.BeginInvoke();

    # wait for the script to finish
    While (-not $result.IsCompleted) { Start-Sleep -Milliseconds 100; };

    #set the error message to the label content
    if ($psComp.InvocationStateInfo.Reason.Message -ne $null)
    {
    #dostuff...
    $lblCoPickFooter.Content = $psComp.InvocationStateInfo.Reason.Message.ToString();

    } else
    {
    # we got something, process the results
    $psComp.EndInvoke($result);
    }
    #cleanup thread and runspace
    $rsData.Close();
    $psComp.Dispose();

    This code shells out to a new thread and passes back the object in a synchronized hash table. What would be nice is if i could just add the using statement and then create new instances of the class without having to do all the syncHash mumbo jumbo.

    Anyways, thanks for making such a great language that is fun to work with!

  • #36062
    Profile photo of Don Jones
    Don Jones
    Keymaster

    So, just so we're all on the same page, PowerShell.org is an independent, community-owned and -operated website. It isn't run by Microsoft, and the product team members don't really lurk here.

    You might consider https://windowsserver.uservoice.com/forums/301869-powershell, which is the official feedback page for the Windows PowerShell product.

    I'll point out in advance that classes in PowerShell v5 are still in very early days. They were implemented primarily to help make DSC resource authoring more consistent; they've got a lot of growth still ahead of them.

  • #36068
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    There _is_ a "using module" statement that's in PSv5 right now, but it's a little bit flaky to use, and I'd recommend not trying to export / share classes across modules yet if you can avoid it. However, this doesn't prevent you from return _instances_ of the class. That works just fine, and you should be able to just output your [CompanyService] object down the pipeline without needing the synchronized hashtable.

    The only thing the "using module" statement will help with us resolving the class name in expressions such as [ClassName]::SomeStaticMethod() .

  • #36070
    Profile photo of Jeremy Murrah
    Jeremy Murrah
    Participant

    As to your parameter validation question, I don't think you need to do it. Casting your property as your enum type sort of handles the validation for you. So for this class:

    enum LogProviders {
     Uninitialized = 0
     TextFile = 1
     EventLog = 2
     Database = 3
     }
    
    Class MyClass {
        [LogProviders]$p
        [String]$cs
    
        MyClass(){}
        MyClass(
            [LogProviders]$p,
            [string]$cs
        ) {
            $this.p = $p
            $this.cs = $cs
        }
    }
    

    These parameters work:

    [MyClass]::New('TextFile','anystring')
    [MyClass]::New('Uninitialized','anystring')
    [MyClass]::New('Database','anystring')
    

    But this one throws a friendly error

    [MyClass]::New('notinenum','anystring')
    

    Cannot convert argument "p", with value: "notinenum", for ".ctor" to type "LogProviders": "Cannot convert value "notinenum" to type "LogProviders". Error: "Unable to match the identifier name notinenum to a valid
    enumerator name. Specify one of the following enumerator names and try again:
    Uninitialized, TextFile, EventLog, Database""

You must be logged in to reply to this topic.