How to turn your PowerShell scripts into building blocks
- select the contributor at the end of the page -
If you're writing a disposable script of sorts-and you don't foresee a future for it-I encourage you to go against your initial inclination of just getting it done and moving on. In fact, I implore you to stop thinking of scripts as disposable. Instead, consider them as building blocks that can be used in addition to other scripts to create a bigger solution in the future.
Using few, if any, hard-coded values in your scripts is one scripting methodology you can subscribe to that will get you away from the disposable script mentality. When you hard-code values in your script, you're essentially solidifying the code and making it much harder to build upon if you need additional functionality later on. Let me give you an example.
Let's say we have 10 servers in Active Directory named SERVER1 through SERVER10, and let's say we need to find all Event ID 462s in the application event log on servers one through eight. At first glance, you might think this is a very specific script. Here are the values we'll work with:
- SERVER1,2,3,4,5,6,7,8
- Event ID 462
- The Application event log
Knowing this, we could write the following script, and I suppose this could get us what we're looking for.
Get-EventLog -Computername SERVER1 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER2 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER3 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER4 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER5 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER6 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER7 -InstanceId 462 -LogName Application
Get-EventLog -Computername SERVER8 -InstanceId 462 -LogName Application
Of the multiple issues with this script, the first that sticks out is all the hard-coded values. We're using the actual server names in the -Computername parameters, the actual event ID in the -InstanceId parameter, and the actual event log name in the -LogName parameter. This gets us what we need today. However, let's say two weeks from now we suddenly need a script to get event IDs 462, 334, and 123 in the Application and System event logs on all of our servers. Oh, and in the meantime, our company has decided to buy another company, which has increased our server footprint by a factor of 10, so now we've got 100 servers!
Are we really going to copy and paste each Get-EventLog log over and over again, adding in each new value? Can you see we're approaching diminishing returns here? Notice if we continue doing this, we're not creating building blocks; we're essentially creating an entire house with the foundation, walls and ceiling in one piece-making it impossible to realistically add any kind of functionality to our script.
That said, let's approach this another way. Let's set out to create building blocks instead of an entire house. We'll use the same requirements as before; eight servers, one event ID, and one log name. This time, let's consider the future usability of the script instead of just our immediate need. How would we go about this?
When writing a script, my mind always thinks in absolutes, and I limit my results from there. What do I mean by this? In this example, we have 10 servers, but we only need to query eight of them. Rather than statically defining all eight servers individually, how about dynamically querying them from Active Directory?
$Servers = Get-ADComputer -Filter "OperatingSystem -like '*Server*'"
In a single line, we now have a collection of all servers in our Active Directory environment. Not only that, but we will always have all the servers as more get added. No code change necessary! However, you say we only want to query SERVER1 through SERVER8, right? All we need to do is place a limit.
$TodayServers = ‘SERVER1','SERVER2','SERVER3','SERVER4','SERVER5','SERVER6','SERVER7','SERVER8'
$Servers = Get-ADComputer -Filter "OperatingSystem -like '*Server*'" | Where-Object { $TodayServers -contains $_.Name }
We now only have the servers we're interested in. Next, how would we go about dynamically including the event ID and the event log name? Check out the following script. Notice we're creating variables instead of hard-coded values, and we're also adding these to the top of the script. This way, we can easily find and modify them later.
$TodayServers = ‘SERVER1','SERVER2','SERVER3','SERVER4','SERVER5','SERVER6','SERVER7','SERVER8'
$TodayEventIds = ‘462'
$TodayEventLogs = ‘Application'
$Servers = Get-ADComputer -Filter "OperatingSystem -like '*Server*'"
$Servers = $Servers | Where-Object { $TodayServers -contains $_.Name }
We've stored everything in variables, and we're ready to get the actual data we need for today. Let's give it a shot.
$TodayServers = ‘SERVER1','SERVER2','SERVER3','SERVER4','SERVER5','SERVER6','SERVER7','SERVER8'
$EventIds = ‘462'
$EventLogs = ‘Application'
$Servers = Get-ADComputer -Filter "OperatingSystem -like '*Server*'" | Where-Object { $TodayServers -contains $_.Name }
foreach ($Server in $Servers) {
foreach ($Log in $EventLogs){
Get-EventLog -Computername $Server -InstanceId $EventIds -LogName $Log
}
}
You'll notice we've added a foreach loop. This loop is iterating over each of the servers. After it has processed a server, it then iterates over each of the event logs in the $EventLogs variable. We're also using the capability of the -InstanceId parameter to accept an array instead of a single string.
This code will get us exactly the same output as those eight previous lines, but it sets up the script to process unlimited servers, event logs, and event IDs! Now, let's see how the script performs when those requirements change. If you'll recall, we now need event IDs 462, 334 and 123 in the Application and System event logs on all of the servers. Rather than us acting like a copy-and-paste machine, it's just a matter of removing the server “filter” and adding a few items into the $EventIds and $EventLogs arrays.
$EventIds = ‘462','334','123'
$EventLogs = ‘Application','System'
$Servers = Get-ADComputer -Filter "OperatingSystem -like '*Server*'"
foreach ($Server in $Servers) {
foreach ($Log in $EventLogs) {
Get-EventLog -Computername $Server -InstanceId $EventIds -LogName $Log
}
}
Done! You can see that we just removed the Where-Object script block, which was limiting the server name results, so now our script can add each event ID and the event logs into their respective arrays. That was much easier than copying and pasting endlessly, right?
If you're a beginner, it might take a while to wrap your head around this concept. But it's important to question your process whenever you find yourself copying and pasting the same code repeatedly, or passing hard-coded values directly to your cmdlet parameters.