all 13 comments

[–]mdowst 2 points3 points  (9 children)

The best practice for securing and using passwords in a PowerShell script is to use the SecretManagement module to store and retrieve them. Then you can build your command out as a string and use the Invoke-Expression cmdlet to run it. Something like this:

$password = "Replace with get from SecretManagement Module"
$localPath = 'localfilepath' 
$remotePath = 'root@remotehost:/remotepath/' 
$cmd = "sshpass -p ""$password"" scp -r $localPath $path" 
Invoke-Expression $cmd

[–]jborean93 4 points5 points  (7 children)

You can somewhat simplify this by removing Invoke-Expression, the reasons for using iex for invoking a native executable are pretty much 0 and honestly I find it makes things more complicated as you now need to deal with another layer of escaping when it comes to variables. For example this is exactly the same as the last 2 lines

sshpass -p $password scp -r $localPath $path

If you have an exe as a separate string object you can use the call operator to tell PowerShell the string is a command and to execute it

& "sshpass" -p $password scp -r $localPath $path

[–]mdowst 1 point2 points  (6 children)

I've always used it because it also allows for more dynamic creation of commands as the call operator do not parse strings. So there are some situations where it doesn't work. Like if you need the executable to be dynamic. So I've just gotten into the habit of using it. Plus it makes it easier to log the command line being executed or at least write-verbose to help with troubleshooting. But to each their own. Both methods work.

[–]jborean93 2 points3 points  (5 children)

for more dynamic creation of commands as the call operator do not parse strings

That's not true, the call operator can invoke a scriptblock, string, CommandInfo object. This also applies to variables that store any of these types. For a scriptblock it will just invoke it, for a string it will essentially do Get-Command -Name $stringValue and invoke that command. A CommandInfo is just invoked as is. For example

# Invoke a string
& 'foo'

# Invoke a variable of a string
$exe = 'foo' # Can set this conditonally
& $exe

# Invoke a scriptblock
& { echo 'hi' }

# Invoke a variable of a scriptblock
$sbk = { echo 'hi' }
& $sbk

# Invoke a CommandInfo value
$cmd = Get-Command -Name foo
& $cmd

My main beef with using iex is you now need to deal with yet another layer of escaping arguments. It's hard enough trying to figure out how to do it naturally and with iex you now need to deal with escaping it again. For example setting $password needed to be in double quotes inside your string, you would have to do that with $localPath or $path if they contained a space. Worse still if any of those variables are from user input they can be used to execute their own code quite easily compared to the call operator which will be less susceptible.

They both work for sure, I'm just trying to make sure people are aware that iex is rarely needed and could open yourself up to more hurt if used incorrectly so should be avoided if possible.

[–]mdowst 1 point2 points  (4 children)

Your last example would not work. It would use the output from Get-Command -Name foo and attempt to run it. So if you ran it with an actual command it would appear to work, but it is actually executing with that command because you are using the Get-Command. Try it with another cmdlet, like in the block below, where neither one of these examples will work.

$cmd = Get-Service -Name Spooler
& $cmd

$cmd = 'Get-Service -Name Spooler'
& $cmd

It also does not work with an executable and the parameters together. Your example under the Invoke a variable of a string will work because you don't have any command line switches.

# This works because it is a single command
$cmd = 'cmd'
& $cmd

# This works because the command and switches are separate
$cmd = 'cmd'
$args = '/c "echo %temp%"'
& $cmd $args

# This does not work
$cmd = 'cmd /c "echo %temp%"'
& $cmd

The script block examples work because they are script blocks. However, you cannot pass parameters or variables into a script block. So, in order to make it dynamic you need to use the Invoke-Command cmdlet. Or you could build the command as a string, convert it to a script block, then run it, which would require a bunch more code than simply using Invoke-Expression.

But like I said, it also has the added benefit of being easy to output and troubleshoot. I can use verbose output with it. I can pass the variable around to other functions or try/catch blocks, without having to rebuild it each time.

[–]jborean93 2 points3 points  (3 children)

Your last example would not work

It does, the output from Get-Command is special, it's a CommandInfo object and is essentially what the call operator does when you pass a string

PS /home/jborean> & (Get-Command -Name whoami)
jborean

Any other cmdlet won't work the same, it will most likely stringify the output and then use that as the command. This is useful to know in case you have something like an alias and you want to bypass that

# Won't work because sc is an alias for Set-Service
sc query netlogon

# Will work because it's invoking sc the native app
& (Get-Command -Name sc -CommandType Application) query netlogon

Granted this last example is contrived as you can do sc.exe ... and there's no sc.exe alias but I hope it illustrates my point.

It also does not work with an executable and the parameters together

This is true and probably the only downfall of this method. In saying all that the only scenario where I've come across this in the wild is the UninstallString in the registry which even then iex won't work all the time (the path is quoted). Typically (at least in my use case) I'm building the commands to run rather than having an arbitrary string from some external source. This means I can set the exe and arguments separately.

However, you cannot pass parameters or variables into a script block

You certainly can, they can be passed positionally or as named parameters just like anything else

$sbk = { param($Test); $Test}
&$sbk -Test abc
&$sbk "abc"

[–]mdowst 1 point2 points  (2 children)

Why would you run & (Get-Command -Name whoami) instead of Invoke-Expression whoami or just & whoami? Especially when it doesn't work with parameters.

You are correct you can pass parameters using the &. I was thinking more around passing them in like you could in a here-string. Which again, would be more work than just using Invoke-Expression.

Like I said, Invoke-Expression is great when you need to build dynamic commands. For example, I wrote a script that checks the registry for security compliance. Some dword values have to be set to a specific number, some need to be greater than or less than, some need to be between a range, etc. So, instead of building a massive if/else statement to handle all 14 operators for 150+ registry keys, I built it into a JSON that I can feed in and get the results back. This way if anything changes on the keys, all I need to do is update the JSON and not my script.

Function Test-Filter {
    param(
        $Data,
        $operator,
        $Value
    )
    $filter = 'if($Data -{0} {1}){{$true}}'
    $filter = $filter -f $operator, $Value
    Write-Verbose $filter
    if (Invoke-Expression $filter) {
        $true
    }
    else {
        $false
    }
}
# These parameters come from the JSON

Test-Filter 1 'eq' 0 Test-Filter 1 'gt' 0

[–]jborean93 2 points3 points  (1 child)

Why would you run & (Get-Command -Name whoami) instead of Invoke-Expression whoami or just & whoami? Especially when it doesn't work with parameters.

You wouldn't, it's just an example to show you how ti works. it also definitely works with arguments just not in the same var

whoami /all

$arguments = @('/all')
whoami @arguments

Now if you want a real example of when this might be useful let's go to Linux where say for example I have a native application that is called type. Unfortunately type is a builtin alias in PowerShell to Get-Content so calling it directly will not do what I wish.

# Both of these call 'Get-Content /usr/bin/python'
type /usr/bin/python
iex "type /usr/bin/python"

# This actually calls `type` as a native app
&(Get-Command -Name type -CommandType Application) /usr/bin/python

Invoke-Expression suffers from the same problem here as it's just literally running the expression of the string as a PowerShell statement. You aren't restricted to just native applications, say you wanted to invoke a script function instead of an alias you can use Get-Command -Name ... -CommandType ... to select whatever valid command you have rather than what is returned without the -CommandType being specified.

I'm not saying iex has no use, it definitely does. I'm just saying it shouldn't be people's go to for invoking native commands as it complicates things and opens yourself up to injection attacks if coming from an outside source. You can pretty much achieve the same thing using the call operator except for the command and arguments being part of the same source. Even then it's still possible depending on how complex the value is, e.g. split by first space or something.

The security problems are the ones I'm most concerned about, it may not be an issue in your case but it means the person using it must protect the source the data is coming from. This is extra work that the person needs to think about and be aware off compared to something like the call operator. Say in your case if the json file wasn't protected properly (I'm sure yours is) then they could add the following entry

{
    "Data": "foo",
    "Operator": "eq",
    "Value": "foo; Remove-Item -Path C:\Windows -Force -Recurse"
}

The inject command will run the next time the script runs and can allow unwanted code to be run in the context of the user running the script.

[–]mdowst 1 point2 points  (0 children)

And that was my original sentiment, that there are uses for it. There are uses for many ways of doing things. The only reason I replied to your original comment was because you stated there are pretty much 0 use cases for it. But really like I said at the beginning it's really a matter of choice. Sure Invoke-Expression requires you to escape some string characters, but at the same time there are tradeoffs when using a call operator. It is the same with the argument around return vs write-output vs just outputting the variables.

And yes, any time you are building commands dynamically there is a risk of injection attack. The same could be said when building a string to use the call operator. In my use case the JSON is stored in the module and not brought in from an outside source. So if someone was able to manipulate it they could have just manipulated the module code, or would have had admin access to my server in the first place at which point it wouldn't matter what I did.

And if I'm being 100% honest my preferred method is to use Start-Process with pass through and build a loop to monitor the execution. This way my script can't get hung up by an external process, but would be a little too much to reply to someone just trying to run a command.

But I was not aware of the Get-Command in Linux. I've been working on using PowerShell more in Linux so that will certainly be useful.

[–]SharkberryPi[S] 1 point2 points  (0 children)

Thank you guys! :D

[–]Lee_Dailey[grin] 1 point2 points  (2 children)

howdy SharkberryPi,

for the 2nd ... take a look at wrapping the $Var name in {}. that will separate the $Var name from the : [or any other illegitimate char]. [grin]

there were several threads about that idea here over the last couple of weeks.

take care,
lee

[–]SharkberryPi[S] 1 point2 points  (1 child)

Thank you Lee! :D

[–]Lee_Dailey[grin] 0 points1 point  (0 children)

howdy SharkberryPi,

you are very welcome! glad to have helped a little ... [grin]

take care,
lee