Passing ‘cmdlet -options’ as Function Arguments

Welcome Forums General PowerShell Q&A Passing ‘cmdlet -options’ as Function Arguments

Viewing 12 reply threads
  • Author
    Posts
    • #224808
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Hello,

      I’m looking for a quick bit of help with a really simple homegrown function. The purpose of the script is unimportant, it works and I shan’t embarrass myself publishing a ‘Fisherprice’ example on a forum such as this!!

      The function takes any number of arguments, parameters, and the final two are always file names. The former arguments I wish to pass through ‘as is’ and use as ‘options’ to a cmdlet. E.g.:

      Code fragment:

      $s_options = $args[0..($args.length - 3)] -join ' ' # Concatenate all the args but the last two, separate by a space
      compare-object $s_options $OBJECT1 $OBJECT2 # This is where is goes wrong, with no 'options' it works fine.

      I’ve ‘echoed’ the offending line and it shall correctly contain “-IncludeEqual” or whatever I attempt to pass in but when I execute in the function it throws-up.

      I must assume I’m missing a trick here, can anyone explain why the cmdlet refuses to ‘see’ the $s_options string as proper options?

      Cheers.

    • #224829
      Participant
      Topics: 4
      Replies: 2249
      Points: 5,494
      Helping Hand
      Rank: Community MVP

      Martin, welcome to Powershell.org. Please take a moment and read the very first post on top of the list of this forum: Read Me Before Posting! Youโ€™ll be Glad You Did!.

      When you post code or error messages or sample data or console output format it as code, please.
      In the “Text” view you can use the code tags “PRE“, in the “Visual” view you can use the format template “Preformatted“. You can go back edit your post and fix the formatting – you don’t have to create a new one.
      Thanks in advance.

      This is where is goes wrong, with no โ€˜optionsโ€™ it works fine.

      What does that exactly mean? What do you want to compare and what should be the result? Compare-Object compares 2 objects. Are you getting errors? Please share them.

    • #224859
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Hi Olaf,

      Thanks for taking time to reply, I rushed through the first post and didn’t absorb the bit about code fragments but sorted now.

      The issue is the ‘$s_options’ part, and it doesn’t need to be in a function as I can force the error from the command line as follows (run the same compare-object twice but the second time passing the option as a variable):

      PS D:\Downloads\Linux and Scripting Stuff\PowerShell\play> Compare-Object -IncludeEqual .\File1.txt .\File2.txt 
      InputObject SideIndicator
      ----------- -------------
      .\File2.txt =>
      .\File1.txt <=
      
      PS D:\Downloads\Linux and Scripting Stuff\PowerShell\play> $s_option="-IncludeEqual"
      PS D:\Downloads\Linux and Scripting Stuff\PowerShell\play> $s_option
      -IncludeEqual
      
      PS D:\Downloads\Linux and Scripting Stuff\PowerShell\play> Compare-Object $s_option .\File1.txt .\File2.txt
      
      Compare-Object : A positional parameter cannot be found that accepts argument '.\File2.txt'.
      At line:1 char:1
      + Compare-Object $s_option .\File1.txt .\File2.txt
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : InvalidArgument: (:) [Compare-Object], ParameterBindingException
      + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.CompareObjectCommand
      
      PS D:\Downloads\Linux and Scripting Stuff\PowerShell\play>

      Forget the fact I have not extracted the file contents, the function is doing that but the issue here is identical in that I cannot express the cmdlet option (-IncludeEqual) as a variable. The error is the last part of the code post above. can you help explain what I’m doing wrong?

      Cheers.

       

    • #224865
      Participant
      Topics: 4
      Replies: 2249
      Points: 5,494
      Helping Hand
      Rank: Community MVP

      Ahhh …. now I know what you mean …. like you want to run the following

      $Bla = '-Recurse'
      Get-ChildItem -Path C:\Windows $Bla

      … and expect to get all files including from subdirectories, right?

      You can use Invoke-Expression do treat strings like code:

      $Bla = '-Recurse'
      Invoke-Expression "Get-ChildItem -Path C:\Windows $Bla"
    • #224868
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Bang on… but now we get to the really tricky part, as I did actually stumble across the ‘invoke expression’ command but fell short once again. The issue I have is that ALL my arguments are variables in the function.

      The compare-object cmdlet requires two objects, funny that, but when I enter these into the Invoke-Expression command they are evaluated along with the option variable and result in a different error. Take your own example but go one step further:

      $Bla = "-Recurse"
      $Bla2 = "C:\Windows"
      Get-ChildItem -Path $Bla2 $Bla

      Oops, forgot the important bit so updating the post ๐Ÿ™‚ Now try the invoke expression:

      Invoke-Expression Get-ChildItem -Path $Bla2 $Bla

      You do get an error as well, same reason I expect as the resolved variable is no longer correct once ‘in’ the invoked expression?

      Cheers.

    • #224883
      Participant
      Topics: 4
      Replies: 2249
      Points: 5,494
      Helping Hand
      Rank: Community MVP

      … quotes? ๐Ÿ˜‰

      Invoke-Expression "Get-ChildItem -Path $Bla2 $Bla"
    • #224886
      Participant
      Topics: 4
      Replies: 2249
      Points: 5,494
      Helping Hand
      Rank: Community MVP

      There might be a better way … don’t you like to explain what you’re actually trying to do?

    • #224898
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Hey, I have no doubt there is a better way, this is my first script/function!! I’ve set about updating my profile to replace the ‘diff’ alias with my own function which replicates the UNIX original (our little secret ๐Ÿ˜ฎ ):

      function f_diff {
      
      if ($args.length -lt 2)
      {
      "USAGE: diff [Compare-Object options] File1 File2"
      return
      }
      $s_filename1 = $args[$args.length - 2]
      $s_filename2 = $args[$args.length - 1]
      if (Test-Path -Path $s_filename1 -PathType leaf)
      {
      $s_contentsfile1 = get-content $s_filename1
      } else
      {
      "ERROR: diff File1 is not a file!"
      return
      }
      if (Test-Path -Path $s_filename2 -PathType leaf)
      {
      $s_contentsfile2 = get-content $s_filename2
      } else
      {
      "ERROR: diff File2 is not a file!"
      return
      }
      if ($args.length -gt 2)
      {
      $s_options = [string]$args[0..($args.length - 3)] -join ' '
      invoke-expression 'compare-object $s_options $s_contentsfile1 $s_contentsfile2'
      }
      else
      {
      compare-object $s_contentsfile1 $s_contentsfile2
      }
      return
      }

      I’d be interested to know if there is another way but from a learning perspective this is great as the issue with invoking the ‘compare-objects’ by passing parameters continues to persist, as the string object variable resolve to strings and fail (I think). If you could help resolve this last issue (UPDATE: the offending line is the ‘invoke-expression’ 8 lines from the end) that would be fantastic, and a better way of doing the same a bonus.

      I stripped all comments to compress it, sorry about that. Cheers.

    • #224907
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Hey Olaf,

      I have cracked the script, not elegant but it works. I do not try to pass the objects through the ‘invoke-expression’ as this fails with them being resolved before the expression is executed. Instead I have passed the filename and then execute the ‘get-content’ with the invoke like this:

      invoke-expression "compare-object $s_options (get-content $s_filename1) (get-content $s_filename2)"

      This does it but I feel as though I have cheated, it must surely be possible to pass a reference to an object into the command rather than resolve it and ‘explode’ it’s contents into the command? That’s the first part, the second is I’m sure there is a neater way to do the whole thing, but as a learning exercise I’ve ended up doing a whole lot more!!!!

      Cheers!

    • #224910
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Final version of the ‘diff function exercise’ looked like this:

      function f_diff {
      if ($args.length -lt 2)
      {
      #Usage Error
      "USAGE: diff [Compare-Object options] File1 File2"
      return
      }
      $s_filename1 = $args[$args.length - 2]
      $s_filename2 = $args[$args.length - 1]
      if ((-Not (Test-Path -Path $s_filename2 -PathType leaf)) -or (-Not (Test-Path -Path $s_filename1 -PathType leaf)))
      {
      "ERROR: Either $s_filename1 or $s_filename2 are not a file!"
      return
      }
      if ($args.length -gt 2)
      {
      $s_options = [string]$args[0..($args.length - 3)] -join ' '
      }
      invoke-expression "compare-object $s_options (get-content $s_filename1) (get-content $s_filename2)"
      return
      }

      Looks a lot simpler now, but masks a great learning exercise and better understanding of how PowerShell manages object variables. Thanks for the pointers, got there in the end. Cheers.

    • #224913
      Participant
      Topics: 4
      Replies: 2249
      Points: 5,494
      Helping Hand
      Rank: Community MVP

      Hmmm … again … quotes? ๐Ÿ˜‰

      invoke-expression "compare-object $s_options $s_contentsfile1 $s_contentsfile2"

      In Powershell there is a distinct difference between single and double quotes. If you like to get variables expanded you will have to use double quotes.

      If I got it right you try to mimic the syntax of a linux command, right? If it is to save keyboard hits you may think about that you don’t have tab completion anymore with your approach. Instead you could simply create another alias for Compare-Object or – and that would be my recommendation – you could create a so called proxy function.

      Edit:
      Wow … you’re fast … ๐Ÿ˜‰ I’m glad you solved it.

    • #224925
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Hey, I’m glad I (we) got there in the end but it was luck more than judgement on my part.

      Quotes didn’t do it in this case, it is odd but no matter how hard I tried I was not able to pass the output of get-contents as a variable (expanded and messed it all up). Google is your friend, and I stumbled on a post embedding the get-content in the compare-object statement… so I cannot take credit for that but it allowed me to ‘invoke’ the command which was still needed to expand the options variables when included; that was a handy trick indeed thanks!

      Auto completion/tab works but that might be again luck more then judgement. I alias my functions, never call them direct (simply because I insist on naming them all f_….). The result is the tab feature to cycle through files etc. actually works, that is pure luck.

      I learned a tonne of things having set off to complete what I thought would be a simple exercise, and just when I thought it was done and dusted… you said something about a ‘proxy function’. Thanks for your help, I’m now off to google proxy func…….. ๐Ÿ˜€

    • #224994
      Participant
      Topics: 2
      Replies: 8
      Points: 61
      Rank: Member

      Final word on this I promise!

      I slept on this and finally thought this through, very useful/important to newcomers like myself. You can preserve the object variable when passing through the ‘invoke-expression’ command by escaping it and passing the variable as a text string e.g.:

      $o_fc1 = Get-Content .\File1.txt
      $o_fc2 = Get-`ontent .\File2.txt
      $s_options = "-IncludeEqual"

      Invoke-Expression “Compare-Object $s_options \$o_fc1 \$o_fc2”ย  ย  # (NB: I can’t ‘preformate’ escape/tick char so replaced with backslash)

      This worked perfectly

      The $s_option is expanded straight away so before executing the invoke expression, allowing the compare command to see it as a literal ‘-option’. Both file-contents object variables were preserved by escaping the “$”; they were not resolved by the invoke-expression but instead afterwards by the compare-object as desired.

      It’s always easy after you work it out! ๐Ÿ˜€

Viewing 12 reply threads
  • You must be logged in to reply to this topic.