Custom commands
Let's create two custom commands. One to delete all files in a directory tree older
than a specific number of days and the other to backup all sql server databases. These
commands could be used to backup sql server files and then subsequently delete obsolete backup files.
Our two custom commands look like this:
''Deletes all
files older than a specific number of days from a directory tree
Command MyDeleteFilesOlderThan(Path,Days)
Loop File,[SubFileTree [Param Path]]
If [FileCreationDate
[Var File]]<[SubtractDays
[Param Days]] Then
DeleteFile [Var File]
End If
End Loop
End Command
''Backs up all
SQL server database files
Command MySqlBackup(InstanceName,Destination)
StopService [Param
InstanceName]
SyncDir [ProgramFilesDir]\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA,[Param Destination]
StartService [Param InstanceName]
End Command
A custom command is a script block between a "Command" and "End Command" statement.
We have decided to call our two commands "MyDeleteFilesOlderThan" and "MySqlBackup",
both of which take two parameters. If a command takes no parameters, an empty
set of parenthesis must be used. Inside the command, the "Param" function can be used
to extract the parameter values for the command.
In the screenshot below, the above script lines were typed in to a new script.
While keying in letters, something interesting happens. The script editor will at
all times automatically sense when custom commands and functions are typed in or
changed. The two custom commands we just typed in, are now showed along with all
other commands and functions in the engine browser tree. All custom commands and
functions will be shown under a root item called "Custom Commands and Functions".
`
So now our two commands behave just like any other command, so if "M" is typed, our two new
commands will show up in the command completion window:
`
The command script blocks can be placed anywhere in a script or in included files. When using
our new commands, our script lines calling them can be before or after the command blocks.
So putting our two new commands to work could look like this:
`
Observe how the context helper now showed a useful help to using our "MyDeleteFilesOlderThan"
command. Where did that come from? Let's look at the script again:
''Deletes all
files older than a specific number of days from a directory tree
Command MyDeleteFilesOlderThan(Path,Days)
Loop File,[SubFileTree [Param Path]]
If [FileCreationDate
[Var File]]<[SubtractDays
[Param Days]] Then
DeleteFile [Var File]
End If
End Loop
End Command
At the location of starting command or function block, the script editor looks at
the previous line. If this line is a comment, this comment is used in the context
helper in relation to this custom command or function. A comment block
starting with /* and ending with */ can be also used to create multi-line help.
Custom functions
Custom functions are defined much the same way custom commands are, using a "Function" and
"End Function" block instead. The main
difference is that a function has to return a value. This is done with the "Return"
command. A custom command can also use the "Return" command without parameters,
in case it has to exit prematurely. The return value a function is not limited to
one value; a list of values can also be returned, effectively making it a custom collection.
Let's try a scenario with a custom function. In a logon script, I would like to collect
system information from clients, including screen information. Investigating "Screen" in the
engine browser, reveals that I can get information about screen resolution, but I would also
like to know if some users are still using legacy non-wide screen displays. This could of course be
determined by collecting heights and widths, but it would be easier to just implement my own
"ScreenRatio" function that I consider missing. So let's try that:
''Returns the
ratio of the screen width and height
Function ScreenRatio()
Return [Round [Calc [ScreenWidth]/[ScreenHeight]],2]
End Function
WriteXMLValue \\AcmeServer\Log$\Screens.xml,/ScreenRatios/[ComputerName],[ScreenRatio]
The engine already provides the functions to get height, width and the required math functions. So we
simply divide the width and height, format that nicely with two decimals and return the result. We then
use WriteXMLValue to log values from the logon script, using an XML file to make sure that we only keep
one value per client, as values per computer are overridden in subsequent logons. With three example
computers, the XML could look like this, revealing that PC8 is indeed using a legacy 4:3 screen:
<ScreenRatios>
<PC1>1.6</PC1>
<PC7>1.78</PC7>
<PC8>1.33</PC8>
</ScreenRatios>
As a footnote, observe that your custom functions and commands are slightly differently colored in the
syntax highlighting. This way you do not need to investigate further, to determine if it is a custom
function or not.
Encapsulation
Our last example was implemented as a custom function.
The same result could have been produced with a single script line:
WriteXMLValue \\AcmeServer\Log$\Screens.xml,/ScreenRatios/[ComputerName],[Round [Calc [ScreenWidth]/[ScreenHeight]],2]
This is much shorter, so why bother to split it at all? The answer is that in many cases,
there might be no point to splitting it. If you are sure you will not need ScreenRatio ever
again, it might be overkill. But if you suspect you might need any given functionality
elsewhere again, it makes good sense. Suppose our function was 10-20 lines and much more
complex. Then it makes good sense to encapsulate it into its own custom command or function
with a simple interface. This will make scripts, where it is used, much easier to maintain.
Custom commands and functions are fully resolved through included files hierarchies.
As more and more custom commands and functions are built, it will be a good idea to
collect these in one or more include files. And if multiple administrators work on
scripting, the encapsulation of complexity will make script construction a lot easier.
One administrator knows how to create a script snippet that deletes files older than
a specific number of days. The other administrators do not need to know how to do this -
they can just call a command with two parameters.
If we put our example custom commands and functions presented here into its own included
script file, we have effectively created a small script library. Our commands and functions
are now encapsulated inside this include file, so our consuming script can be very simple:
Include MyScriptLib.fsh
MySqlBackup MSSQL$SQLEXPRESS,O:\SQLBackups\[Date]
MyDeleteFilesOlderThan O:\SQLBackups,90