Summary: In this how to article, Microsoft Scripting Guy Ed Wilson discusses design considerations for Windows PowerShell functions.
Hey, Scripting Guy! I am trying to decide how to configure my Windows PowerShell function. I have heard that functions should return objects, but I am thinking that it would be better for my function to produce my report. What is correct?
Hello TS, Microsoft Scripting Guy Ed Wilson here. When I think of scripts I generally think about functionality. I have to know what the script is supposed to accomplish. For me the main purpose of a script is to perform a specific action, or to return certain information. I generally try to avoid writing scripts that accomplish multiple tasks. There are several reasons for this, the first is that it makes the script complicated, and complication is the opposite of maintainability and supportability. Therefore, I would probably write one script that enables DHCP on a client workstation, and a different script to set a static IP address on a client workstation.
If I need a script that will provide either the ability to enable DHCP on a client workstation, or the ability to set a static IP address on a client workstation, I will need a way to organize this functionality so that the script will be easy to read and to understand. To do this, I will move the code into separate functions. Therefore, one function will be named Set-DHCP and the other function will be named Set-StaticIPAddress. In this approach to organizing the functionality of the script, the functions behave like traditional scripts, and the script itself is more of a wrapper that is used to expose the functionality that is encapsulated inside the two functions.
When designing functions, one of the things I have to know is if the function is to work remotely or only locally. In the Set-DHCP function and in the Set-StaticIPAddress functions, the technology that will be used is Windows Management Instrumentation (WMI) and therefore there is a provision for connecting to a remote computer and making the configuration changes. The script that would host these two functions, should therefore expose a ComputerName parameter. In addition to providing for remote management, the next design consideration is whether the script should merely impersonate the logged on user who is running the script, or if the script should expose the ability to use alternate credentials. WMI lets you use alternate credentials for remote connections, but not for local connections. Therefore, if the script attempts to perform a privileged operation on the local machine, from a non-administrator account the operation will fail. One thing to keep in mind is that Windows PowerShell is not UAC aware. Therefore, it will not prompt for elevation. It is up to the script writer to know beforehand if the script will require elevated rights. If the script requires elevated rights, the script should check whether the account that is used to launch the script is a member of the administrator group. If the account that is used to launch the script is not a member of the admin group, the script should display an appropriate message and exit gracefully. This check should be the first thing the script does so that the user can take appropriate action. You might decide that you would rather just launch an elevated Windows PowerShell console and try to run the script from there. That, of course, is a design consideration. To me, it is more trouble that it is worth. However, it might make sense in certain circumstances.
If you are not using a technology that supports its own remoting, your function can still operate against a remote machine by using Windows PowerShell’s own remoting functionality. In fact, in many circumstances this will be preferable to using native cmdlet remoting. For example, the Get-Process cmdlet has a ComputerName parameter that can be used to make a remote connection to a target computer and provide information about processes. However, when the Windows Firewall is running in its default configuration, the communication mechanism used by the Get-Process cmdlet is blocked. In addition, the cmdlet does not have a Credential parameter and therefore the only way to run the cmdlet is to impersonate the current logged on user.
On the other hand, if you write a function that uses Get-Process and configure it as if it were only going to run on a local computer, you can use the Windows PowerShell remoting to target a remote machine. The advantage is that the ports that are used by WinRM are configurable and manageable via Group Policy. In addition, the Windows PowerShell remoting cmdlets support alternate credentials for remote connections. This scenario also simplifies the function / script design. You do not have to test for local versus remote connections, nor do you have to provide prompts for computer and credentials. You write the basic function / script, and then allow Windows PowerShell remoting to handle the remote connectivity and alternate credentials.
After I have decided the remote / local question, the next issue to decide is whether to produce a report from within the function. Generally, I do not like to do this. I prefer instead to have one function that performs an action and then returns an object, and then have a different function that produces the report. This makes it easy to add additional functions that may produce additional kinds of reports. All I would have to do is to add an additional function to my script that generates the additional report. The function that returns the object could be used in other scripts or modules as needed.
TS, that is all there is to using functions in Windows PowerShell. Script Design week will continue tomorrow when I will talk about how to identify a source for computer names.
I invite you to follow me on Twitter or Facebook. If you have any questions, send email to me at firstname.lastname@example.org or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy