Using variable from one dynamic parameter within another dynamic parameter

This topic contains 15 replies, has 4 voices, and was last updated by  AJ 2 weeks, 2 days ago.

  • Author
    Posts
  • #98191

    AJ
    Participant

    Hi,

    I have a dynamic parameter accepting a validateset which comes from a config file for my application.

    That parameter ($Instance) is used to select a list of application nodes so I know where I need to deploy an object to. So a user would select their value for $Instance and thats stored in $PSBoundParameters for me to access later.

    With the $Instance, I'm looking to use that to run a "| Where-Object $_.Instance -eq $Instance " where before the pipe I have a function that returns an object which has Instance as a property.

    Essentially, I need to use the value from one dynamic parameter to help me prepare the collection list for another dynamic parameter in the same dynamic parameter block..

    Is what I'm trying to do possible?

    Thanks,

    AJ

  • #98199

    Joel Sallow
    Participant

    Something tells me this is going to be one of those things where it's probably kind of possible but really tricky, and you may be better off explaining why you're trying to have code like this and what the end goal is here, because there's probably a much easier way to go about it! 🙂

    • #98203

      AJ
      Participant

      🙂

      Ok, here's what I'm trying to do

      I have a yaml configuration file which has a tonne of application config within it.

      When I import that config file, I get a hash table acting as my data dictionary. I'm loading this as a module scoped variable because each function in the module references the config file.

      Within the hash table, I have keys that relate to instances of an application I'm automating. As an application admin for this system, I have access to multiple permission domains separated by the application instance.

      So my config file looks like this:

      - instances:
        - HOST1:
          - permissions:
            - readOnly: ADGroup1
            - readWrite: 
              - ADGroup2:
                - perm1
                - perm2
        - HOST1:
          - permissions:
            - readOnly: ADGroup3
            - readWrite: 
              - ADGroup4:
                - perm3
                - perm4
      

      I'm trying to do the following:

      using the above example:

      roll up the read write permissions into a regexoptions to use for a dynamic validatePattern but only return the readwrite perms for the instance the user selects if that makes sense?

      The real kicker I think is that the Instance variable also needs to be a dynamic variable because users will only have access to a subset of instances based on their AD group memberships.

      Does that make sense?

      Thanks,

      AJ

  • #98202

    AJ
    Participant

    🙂

    Ok, here's what I'm trying to do

    I have a yaml configuration file which has a tonne of application config within it.

    When I import that config file, I get a hash table acting as my data dictionary. I'm loading this as a module scoped variable because each function in the module references the config file.

    Within the hash table, I have keys that relate to instances of an application I'm automating. As an application admin for this system, I have access to multiple permission domains separated by the application instance.

    So my config file looks like this:

    - instances:
      - HOST1:
        - permissions:
          - readOnly: ADGroup1
          - readWrite: 
            - ADGroup2:
              - perm1
              - perm2
      - HOST1:
        - permissions:
          - readOnly: ADGroup3
          - readWrite: 
            - ADGroup4:
              - perm3
              - perm4
    

    I'm trying to do the following:

    using the above example:

    roll up the read write permissions into a regexoptions to use for a dynamic validatePattern but only return the readwrite perms for the instance the user selects if that makes sense?

    The real kicker I think is that the Instance variable also needs to be a dynamic variable because users will only have access to a subset of instances based on their AD group memberships.

    Does that make sense?

    Thanks,

    AJ

  • #98206

    js
    Participant

    I know you can't use -pipelinevariable with cmdlets that have dynamic parameters like get-netfirewallrule (bug).

  • #98212

    AJ
    Participant

    I posted something earlier about what I was trying to do but I think its being moderated, I missed the comment about frequently reposting, sorry about that

    I have however come across Register-ArgumentCompleter and seeing if that will do what I need either on its own or mixed with TabExpansionPlusPlus

    Seems a useful video on Youtube from PowerShell summit 2016 by Rohn Edwards. Its a 49 minute video and he ran out of time to go through his last example which I think would have had some more relevant material for what I was after.

    The post I looked at was here – External link to Youtube presentation. He mentions an additional talk the Wednesday after this, does anyone know where I can see the presentation? I *think* I'm following whats going on but seeing the demo along with hearing the thoughts as he's going through would be really handy.

    Thanks,

    AJ

    • #98227

      Rohn Edwards
      Participant

      The other video can be found here

      Example code for both presentations can be found here

      I just peeked at the last example file for the argument completers presentation, and it looks like what I didn't get to was how to access helper functions inside a module's scope.

      I think you're definitely on the right track, though, with looking into argument completers instead of trying to use dynamic parameters. I LOVE dynamic parameters (a big part of that presentation was devoted to them), but I almost always find some other way to implement whatever I thought I needed them for if I'm making a function that will be used by other people. And if you're just after dynamic ValidateSets, I think argument completers are way easier write and maintain than using dynamic parameters anyway. The only real downside is that if you really want to limit the parameter values like ValidateSet would, you have to add code to your function to error out if the user provides invalid input.

  • #98215

    js
    Participant

    I believe he posts here.

  • #98233

    AJ
    Participant

    Thanks for the help so far guys, – Rohn, really useful video from summit! The piece I was hoping was included in the last presentation I think I've now figured out. I'm using Invoke-Build to generate my module from split functions. I've created a tab expansion script (not function) within my public functions folder which gets added to my psm1 at build time. So each time someone imports the module, the Register-ArgumentCompleter runs over the top of previous registrations.

    I noticed (in the first video) you mentioned there was a way to handle this at the parameter level, with an [ArgumentCompleter({})] block on the parameter? I'm interested in that because I cant currently see how ArgumentCompleters would work if you wanted to replace Dynamic parameters if you had different validatesets based on the parametersetname.

    Is there a way to do this?

    Get-Object -Name {"Get-Childitem -path C:\Objects\Object | Select-Object -ExpandProperty BaseName}
    Get-Object -IsDeleted -Name {"Get-Childitem -path C:\Objects\Deleted\Object | Select-Object -ExpandProperty BaseName}

    where the second only looks at the Deleted objects folder when the -IsDeleted switch is provided? Can I only handle this at the parameter level within my function or is there another way with the $commandAst I can obtain this from within the scriptblock when the user is at the command line?

    I take your point about having to internally validate, where the validation needs to be strict, think I saw in your video a switch statement would cover it with the Default state ignoring everything not in the required validateset. That should work for what I need.

    Thanks,

    AJ

  • #98239

    AJ
    Participant

    Ok, I've realised having a tab expansions register containing all of my registrations for my module at the end isn't the only way.

    After the closing brace for the function, within the same .ps1 file I'm registering the ArgumentCompleters there..

    I guess it means my registrations are littered throughout my psm1 file but at least this way I can manage all the registrations whilst generating the functions.

    Guess either works here without too much issue. Is there a better way?

  • #98247

    Rohn Edwards
    Participant

    About where to register the completers: the command and/or parameters don't have to exist yet in order to register argument completers. Try calling Register-ArgumentCompleter before creating your function.

    The [ArgumentCompleter()] attribute can also be used if you want to keep the completion logic closer to the parameter definition. It takes a scriptblock (like [ValidateScript()] would), and the scriptblock should be the same thing you would put into the -Scriptblock parameter to Register-ArgumentCompleter. Be careful, though: your commands won't work for users with PS 3 or 4 unless the user has the TabExpansionPlusPlus module.

    The way you've got it working sounds fine to me. If you've got command definitions separated out into their own files, it makes sense to keep the calls to register the completers near the function definitions.

    Did you ever get the -IsDeleted example you mentioned working? If not, look at the $fakeBoundParameters argument that the completion scriptblock receives. Also, here's an example that's probably closer to what you're looking for.

  • #98253

    AJ
    Participant

    Hey,

    Regarding the IsDeleted – yeah I got that working thanks, I didn't have the registration in place for the IsDeleted parameter so it wasn't re-evaluating when that switch was present. I normally get my parametersetname from PSCmdlet but I should be able to do this given I can obtain the supplied parameters from fakeBoundParameters as you mention.

    Yeah, I have #region and #endregin defined at the bottom of the function definition to define the registrations and keep the module relatively readable to someone who doesn't see the split functions.

    Fortunately, our we have PS 5 across our estate so no issue there for backwards compatibility, I'm using Classes too, so that would have been a problem also.

    The Get-Food article was what led me to your YouTube video, I haven't managed to limit additional parameters based on previous values but making progress, but I'm using that as reference.

    I can't seem to get my head around the filtering aspect and where my data sources are, but once thats clarified, this should be much simpler moving forward.

    Thanks,

    AJ

  • #98254

    AJ
    Participant

    Also, is there a way to view all the registrations available?

    Some of the my functions integrate with other systems and I prefixed them based on my module. I have a function which runs Get-ADDomain from a config file, but that obviously clashes with the ActiveDirectory module.

    If I'd set registrations on Get-ADDomain and didn't specify the command name in the Module\Function format for CommandName, how could I see if I'd overwritten an existing registration and then remove the registration I added and keeping any registrations the ActiveDirectory module applies?

    I guess that's more relevant given you mentioned you can set registrations for functions that don't yet exist.

    Thanks,

    AJ

    • #98281

      Rohn Edwards
      Participant

      I don't think there's a supported way to see the registered completers. Under the hood, I'm pretty sure it's just a dictionary that the PS engine looks at when it's tab completion time. That's why you can register them even though a command doesn't exist. While it's technically possible to get access to the dictionary (or whatever holds the completers), I wouldn't worry about cleaning them up.

      And about the -IsDeleted example: unless I'm misunderstanding something, you shouldn't need to do anything with Register-ArgumentCompleter and that parameter. Instead, the -Name parameter woud get all of the attention. The arguments that are passed into the completer scriptblock give you enough information to know if -IsDeleted is being used. The fifth argument, which is usually called $fakeBoundParameters, is what you'd use 99% of the time, and the fourth argument ($commandAst) can be used when $fakeBoundParameters doesn't work.

      Here's a quick example of what I'm talking about:

      Register-ArgumentCompleter -CommandName Get-Object -ParameterName Name -ScriptBlock {
          param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
      
          $ExtraPathInfo = if ($true -eq $fakeBoundParameters.IsDeleted) {
              'Deleted\'
          }
          else {
              ''
          }
          $Path = "C:\Objects\${ExtraPathInfo}Object"
      
          Get-ChildItem $Path | 
              Select-Object -ExpandProperty BaseName | 
              Where-Object { $_ -like "${wordToComplete}*" } | ForEach-Object {
                  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
              }
      }
      
    • #98293

      AJ
      Participant

      Ah ok, thanks, that's neater than what I was doing, and looks much easier to read.

      I was using a function to generate the completer but I found this to be a little slow and each different parameter was making calls to that function which was compounding the completion time.

      To get around that, I included the Get-* commands in the psm1 file so they're loaded at run time. It turns out I was pushing the variable to the global scope which I didn't really want to have to do. This is really the first time I've used argumentcompleters so I guess having to push into the global scope for quicker tabcompletion might be considered an acceptable exception.

      Is there a way to load the values I'd need for the completers into a module scope that doesn't push into the global scope? I haven't really played with Module scope's massively but if you could do that, you wouldn't have to push the global scope etc.

      I have export-modulemember -variable $VarName but that doesn't look to have a scope property so it seems to always export to the global scope.

      If you have functions wrapped within a psm1 and script blocks included in the psm1 – my understanding is they run in a different scope?

      Thanks,

      AJ

  • #98295

    AJ
    Participant

    Ah ok, looks like if I don't use the Export-ModuleMember -Variable $VarName and specify script: scope this works

    So I think my understanding that functions and scripts from the same psm1 file run in different scopes was wrong.

    Looks like script scope in that instance applies to the psm1 which I think covers what I need

You must be logged in to reply to this topic.