Controlling PowerShell Function (Re)Definition

Controlling PowerShell Function (Re)Definition

  • Comments 8

A question came up in the PowerShell news group recently where someone was asking how they could prevent functions from being redefined.

 

There are a couple of approaches you can use. First you can make functions read-only or constant. This is pretty straight-forward but does have some downsides. The other approach is to check to see if the function exists and only create it if it isn't already defined.

 

First we'll look at defining read-only or constant functions. There isn't a way of doing this with the function keyword in V1.0 of PowerShell. Instead, you have to do this through the function namespace provider using Set-Item (exactly the same way you would make a file read-only by the way):

 

PS (7) > set-item function:bar {"Bar"} -option readonly

PS (8) > function bar {"Dude!"}

Cannot write to function bar because it is read-only or constant.

At line:1 char:13

+ function bar <<<< {"Dude!"}

 

As we can see, trying to define a read-only function will fail. You can, however, use the -force option to force a redefinition:

 

PS (9) > set-item function:bar {"Dude"} -force

PS (10) > bar

Dude

 

The other option is to make a function constant when it is defined:

 

PS (11) > set-item function:bar {"Constant"} -option constant

Set-Item : Existing function bar cannot be made constant. Functions can be made constant only at creation time.

At line:1 char:9

+ set-item <<<< function:bar {"Constant"} -option constant

 

This fails initially because we've already defined "bar" . Use Remove-Item to remove the function, and then it works:

 

PS (12) > Remove-Item function:bar -force

PS (13) > set-item function:/bar {"Constant"} -option constant

PS (14) > set-item function:/bar {"Dude"} -force

Set-Item : Cannot write to function bar because it is read-only or constant.

At line:1 char:9

+ set-item <<<< function:/bar {"Dude"} -force

 

Note that you can't redefine a constant function, even with –force. A constant function will persist until the process terminates.

 

So that's how you define a constant function.

 

Attempting to redefine a costant or read-only function will result in errors. This may be fine, but there are times such as when you are processing the profile files or "dotting" a library script that you don't want to see errors. In this situation, a better way of handling this is to only redefine a function if it doesn't already exist.

 

In PowerShell, functions are bound at runtime. In other words, while the syntax of the function is checked when the file is loaded (complied), the function isn't associated with the name until the script is executed. This makes it pretty easy to control when functions are bound. You can use the Test-Path cmdlet to check to see if the function exists and only execute the function statement if it doesn't already exist as shown:

 

PS (17) > if (! (test-path function:buz)) { function buz { "First" } }

PS (18) > buz

First

PS (19) > if (! (test-path function:buz)) { function buz { "Second" } }

PS (20) > buz

First

 

The first statement above establishes the initial binding for the buz. The second statement checks the definition and doesn't override it since it already exists.

 

Another way to do this is with the New-Item cmdlet. This cmdlet will only create a new item in a namespace if it doesn't already exist:

 

PS (45) > new-item -path function: -name buz -value {"Hi"}

New-Item : The item at path 'buz' already exists.

At line:1 char:9

+ new-item <<<< -path function: -name buz -value {"Hi"}

 

We're still seeing an error which we don't want so we can use the –ErrorAction parameter to suppress this:

 

PS (46) > new-item -path function: -name buz -value {"Hi"} -ea silentlycontinue

PS (47) > buz

First

 

And we can see that the definition hasn't changed.

 

So now we know how to control when functions are defined and redefined. The cool thing about this is that we also now know how to do the same thing with variables. In fact we can do the same thing with any provider the supports the read-only and constant options. And even if the provider doesn't support these features, you can still use Test-Path to prevent over-writing existing items.

 

-bruce

 

Bruce Payette [MSFT]

Windows PowerShell Tech Lead

 

Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell

Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Windows PowerShell in Action (book): http://manning.com/powershell

Leave a Comment
  • Please add 4 and 4 and type the answer here:
  • Post
  • PingBack from http://blogs.flexnetconsult.co.uk/colinbyrne/PermaLink,guid,24fde49d-bc53-4773-a679-2bc62db4cae5.aspx

  • Over at the excellent PowerShell Team Blog, Bruce Payette wrote about Controlling PowerShell Function (Re)Definition.

    However, making a function constant does not prevent it from being shadowed in child scopes. Given that a child scope is created in an..

  • > However, making a function constant does not prevent it from being shadowed in child scopes. Given that a child scope is created in an..

    Correct.  That is what -OPTION ALLSCOPE is for.  This defines the element for all scopes.  Here is an example:

    PS> set-item function:bar {"Constant"} -option constant,allscope

    Jeffrey Snover [MSFT]

    Windows PowerShell/MMC Architect

    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • That's pretty much what I said in my post... unfortunately, the trackback comment here didn't make it obvious that there was more at http://ps1.soapyfrog.com/2007/01/26/making-functions-really-read-only/ :(

  • At the moment it is possible to do the following:

    set-item function:Get-ChildItem {"Bar"}

    > Get-ChildItem

    > Bar

    If a users execution policy allows it (e.g. RemoteSigned or weaker) malicious code could be added to the My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 profile re-writing common functions e.g

    Set-Item function:Get-ChildItem {remove-item C:\*.* -recurse -force}

    I am missing something in my understanding?

  • Once an attacker has code running on a victim's computer, they can do anything they wish. Why play around with their profile, when you can just do the Remove-Item damage right away?

    The important thing to remember is that code running on the computer can do anything that the user can.

  • Speaking of the variable provider it sure would be nice if the function provider borrowed the "Description" property that can be associated with variables.  Then a package could identify the functions that it defines by setting the function Description to a particular value.

Page 1 of 1 (8 items)