2.1. Load and Save ScriptsAll of the examples so far have followed the same procedure: MSH presents a prompt and waits for your input, you type a command, the command is processed, the output is displayed, and the cycle repeats. This is useful for interactive command line use, invoking cmdlets one by one, building pipelines, and general day-to-day use. However, like cmd.exe, MSH can take its instructions from a file instead of receiving commands from a keyboard. This enables us to create complex sequences that perform one or more tasks and save these scripts to disk. In this way, it's possible to build up a library of scripts that encapsulate common repetitive tasks. With this library on hand, it's easy to save time and quickly recall and rerun previously stored scripts.
2.1.1. How Do I Do That?MSH scripts can be stored in simple text files with a .msh extension in much the same way as command-shell scripts can be stored in text files with a .bat or .cmd extension. Using your favorite text editor, let's put together a simple script that filters the output of get-process. Save the script, shown in Example 2-1, as get-processHandlesGt500.msh in a convenient directory. (We'll use D:\MshScripts in the examples that follow.) Example 2-1. get-processHandlesGt500.mshget-process | where-object { $_.Handles -gt 500 } Once saved, the script can be run in the shell simply by typing in its path and name. If the script is located in the current working directory, the current path must also be specified as .\get-processHandlesGt500.msh: MSH C:\> D:\MshScripts\get-processHandlesGt500.msh Handles NPM(K) PM(K) WS(K) VS(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 788 14 13832 16132 70 202.02 1656 CcmExec 764 12 36760 34084 175 20.41 1864 msh 1792 54 22476 24336 106 418.00 824 svchost 557 60 7352 3820 51 58.92 488 winlogon It doesn't stop there: with MSH, a script is part of a pipeline. For example, if we want to rearrange the results of the script and present them differently, it's simply a case of piping to the relevant cmdlets we've already met: MSH C:\> D:\MshScripts\get-processHandlesGt500.msh | sort-object Handles | format-list ProcessName,Handles ProcessName : winlogon HandleCount : 557 ProcessName : CcmExec HandleCount : 785 ProcessName : msh HandleCount : 800 ProcessName : svchost HandleCount : 1792 Now, let's move to a slightly longer example. We'll create a new script, get-processReport.msh, as shown in Example 2-2, that generates a quick snapshot of the current process state and calls out high-priority processes in a separate list. Example 2-2. get-processReport.msh# # This script generates a report about active processes # "Report generated at " + (get-date) "" # insert blank line "Processes sorted by handle count" get-process | sort-object Handles | format-table "" # insert blank line "High memory usage (>100MB)" get-process | where-object { $_.WorkingSet -gt 50000000 } | format-table This time, let's run the script from the current working directory, after making sure that we're in the right folder (D:\MshScripts): MSH D:\MshScripts> .\get-processReport.msh Report generated at 12/1/2005 3:41:06 PM Processes sorted by handle count Handles NPM(K) PM(K) WS(K) VS(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 4344 75 138108 100852 1093 ...82.52 7404 OUTLOOK 1785 54 21852 23708 106 414.66 824 svchost 788 14 13980 16452 70 201.03 1656 CcmExec 560 12 35252 32584 175 17.43 1864 msh 557 60 7352 3820 51 57.46 488 winlogon 438 5 1832 3212 24 236.38 464 csrss 438 10 4728 2228 42 77.94 544 lsass 363 11 9420 13324 58 2,582.09 212 explorer 296 0 0 168 2 328.87 4 System 266 13 1524 3264 34 94.29 756 svchost 261 6 1316 2276 24 41.11 532 services 248 7 2192 2996 37 19.84 940 svchost 222 7 6356 6152 65 10.47 1708 wuauclt 182 5 2316 3276 57 5.83 720 svchost 137 3 1564 3420 23 3.98 320 wmiprvse 132 3 3480 3204 25 9.67 1920 wmiprvse 123 5 1008 2500 32 1.44 1844 alg 96 4 2428 2364 26 2.19 1088 spoolsv 89 4 1064 2252 29 10.04 896 svchost 25 2 600 2228 25 1.10 3572 notepad 25 2 692 2472 25 5.61 1792 notepad 21 1 168 264 4 1.31 308 smss 0 0 0 16 0 0 Idle High memory usage Handles NPM(K) PM(K) WS(K) VS(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 4344 75 138108 100852 1093 ...82.52 7404 OUTLOOK 2.1.2. What Just Happened?MSH recognizes script files by the .msh extension. Upon coming across a script file, MSH will open the file and execute the commands within it line by line. A script isn't any different from typing commands into the shell by hand; it uses the same idea and execution order of a pipeline and is subject to the same output-formatting rules, although there are some subtle differences caused by scoping (which we'll discuss in Chapter 4). In Example 2-1, note that the objects representing the four processes from get-processHandlesGt500.msh are actually emerging from the script into the pipeline. In this manner, it's easy to blend script output with interactive mode and build a pipeline that seamlessly includes aspects of both. Assuming you may reuse a script in the future, it's always good practice to add comments to areas where the behavior might not be immediately obvious later. If you come back to a script in six months, will you still remember exactly why you chose to sort by a given property? Comments can be added to a script by using the pound symbol, #. Once a # symbol appears on a line, any characters following it (on the same line) are ignored when the script is run. To the same end, it's a good practice to use the long form of commands (e.g., get-childitem instead of gci or dir) for legibility. The get-date cmdlet hasn't been introduced yet, but its name should betray its function. When used without parameters, it returns the current system date and time in the short format (as defined by the system's Regional Settings).
2.1.3. What About......Running scripts as Scheduled Tasks? MSH has a command-line option, -command, that takes the filename of a script to run. Instead of loading up into interactive mode, it will immediately execute the script and exit: msh -command D:\MshScripts\get-processHandlesGt500.msh In fact, even the -command part is optional, so just passing the full filename is sufficient: msh D:\MshScripts\get-processHandlesGt500.msh What's more, if the D:\MshScripts folder is in the %PATH% environment variable, just the filename is enough: msh get-processHandlesGt500.msh In addition to the simple script invocation format with just a path and filename, there's another method called dot sourcing. A script file can be dot sourced by placing a period and space (. ) before the script's filename. The distinction is important when variables and functions are used within the script, as the two methods treat variable and function scope differently. We'll look at dot sourcing more in Chapter 3.
2.1.4. Where Can I Learn More?Running the command msh -? will give more details about the available command-line options. |