A Better PowerShell Automation Philosophy

Today’s article is a continuation of a thread that started out discussing the best approach for selecting data from PowerShell, to automating that process. I’m going to continue with the same scenario and code samples, so take a few minutes to peruse my previous posts if you’re just digging in.

As I mentioned last time, the script file I ended up with was basically a playback of PowerShell commands you typed in an interactive session. But there’s much more to PowerShell automation than this example. I’d like to get you thinking about creating reusable tools as modules. These tools will be the building blocks that you can combine to meet what I’m sure are ever-changing needs. In much the same way that a cmdlet like Start-VM is a building block, you can create your own through PowerShell functions. Please keep in mind that this article is more conceptual in nature and not exactly a tutorial on how to write a function.

The most important concept is that a PowerShell function only does one thing and if it sends objects to the pipeline, it only sends one type of object. You would not write a function that emitted service and process objects. In the original scenario, there were a few distinct actions with different types of results. So I would create separate tools. First, here is a function to simply getting running virtual machines that start with a particular string.

I started using variable names as a means of testing and also to make it easier when I move this to a function, which I’ll show you in a moment. I wanted to point out that in this particularly Hyper-V scenario, you might also want to use a regular expression pattern to find virtual machines that are Off or Saved.

While you might be happy with this, wrapping it in a function provides better mechanisms for tasks like error handling and validation.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

#requires -version 4.0

#requires -module Hyper-V

FunctionStart-MyVM{

[cmdletbinding(SupportsShouldProcess)]

Param(

[Parameter(Position=0,HelpMessage="Enter a virtual machine name")]

[ValidateNotNullorEmpty()]

[string]$Name="*",

[Alias("cn")]

[ValidateNotNullorEmpty()]

[string]$Computername=$env:Computername,

[switch]$AsJob

)

#Add ErrorAction to PSBoundParameters

$PSBoundParameters.Add("ErrorAction","Stop")

#remove AsJob from PSBoundParameters

if($AsJob){

$PSBoundParameters.Remove("AsJob")

}

Try{

$vms=Get-VM@PSBoundParameters|Where{$_.state-match"off|saved"}

if($vms){

if($AsJob){

$vms|Start-VM-AsJob

}

else{

$vms|Start-VM

}

}

}#Try

Catch{

#$_ is the exception object

Write-Warning"Failed to get virtual machines on $computername. $($_.exception.message)"

}#Catch

}#end function

I also added a feature to pass the –AsJob parameter to Start-VM. The bottom line with all of this is that I know have two re-usable and flexible tools. I could even create a module to make them even easier to use. Coming full circle, I can still create a script to automate the use of these tools.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#requires -version 4.0

#requires -module Hyper-V

[cmdletbinding(SupportsShouldProcess)]

Param(

[Parameter(Position=0,HelpMessage="Enter a virtual machine name")]

[ValidateNotNullorEmpty()]

[string]$Name="*",

[Alias("cn")]

[ValidateNotNullorEmpty()]

[string]$Computername=$env:Computername,

[ValidateNotNullorEmpty()]

[string]$Path="D:\work\vms.txt"

)

#insert code to dot source or load modules with Get-RunningVM and Start-MyVM functions

The script file wraps up my underlying tools with some parameter validation and support for ShouldProcess.

I appreciate this module approach from a theoretical perspective. This scenario leaves something to be desired, however, from a practical perspective.

When I create PowerShell tools, I try to not repeat steps or commands. In this scenario even though it is in separate commands, I am essentially grabbing virtual machines that match a given name twice. Once to log those that are running and again to start off or saved VMs. On all but the busiest of Hyper-V hosts, this is probably a minor point, but why make two connections when one will do?

With this is mind, the original scripted solution may be more appropriate. Your design decisions may come down to what you need to automate and who will be running it. As you hopefully have recognized by now, there is usually more than one way to accomplish a task in PowerShell.

In fact, I’ll leave you with yet another solution to this particular problem. This is a multi-step solution. There’s no rule that says everything has to be one long pipeline.

This would be easy to wrap up in a script so that you could make it easier to re-run and parameterize items, such as VMName and Computername. If I were to do that, I’d most likely give the script a name like Invoke-DailyVMTask.

Sponsored

This has been a complex series of articles, and I fear I may have left you more confused. If so, please post your questions in the comments and stay connected with the Petri IT Knowledgebase.