Data sections are a PowerShell technique, included since version 2.0, which provides script writers with a powerful technique to separate out code from data. This approach to distinguishing between the two has a long history in other languages, but, in the PowerShell world, falls into a few specific niches. In practice, there are also a few ways to pull it off, yet, aside from some on-the-box Microsoft scripts, there is not a lot of discussion about their usage. After having experimented with data sections a little, I felt some of the gotchas and caveats I ran into may be worth sharing so others dont try to reinvent the same wheel I hacked on.
Data sections, as outlined in the v3 help, are read-only, text strings separated from script logic. Data sections can be stored both in the same file as a script and in a separate file with a .psd1 extension. One compelling reason to separate out data from code is to allow for messages in various languages and/or updatable context-specific data. A specific scenario in which data sections proves useful is code signing. If you have a script for which the logic is complete, but, you need to update the message, you would not need to modify the script which would prompt a script resigning. Instead, you could simply update the .psd1 files strings and the modification would be taken care of.
The general syntax for using a data section is to begin a section with the required DATA keyword., followed by two items:
In our second example, we use a simple DATA section with strings (but no keys). In this case, you need to reference the strings using zero-based, array notation, as shown below:
The second type of use for data sections is with psd1 file (PowerShell data files I presume the acronym to be). To get an idea of where this really fits in, we need to glance briefly at another concept: script internationalization. For enterprise packages, that is PowerShell solutions provided to multiple languages, the code/logic of scripts works the same regardless of the language in which the script is run. What changes, however, are the messages to the end user. While I was originally researching this topic I was looking for ways to handle large text blocks simply and easily. One of the concepts I stumbled upon was the use of locations, otherwise known as code pages, to associate .psd1 file content with scripts.
In my example, I created two files:
Data sections, as outlined in the v3 help, are read-only, text strings separated from script logic. Data sections can be stored both in the same file as a script and in a separate file with a .psd1 extension. One compelling reason to separate out data from code is to allow for messages in various languages and/or updatable context-specific data. A specific scenario in which data sections proves useful is code signing. If you have a script for which the logic is complete, but, you need to update the message, you would not need to modify the script which would prompt a script resigning. Instead, you could simply update the .psd1 files strings and the modification would be taken care of.
The general syntax for using a data section is to begin a section with the required DATA keyword., followed by two items:
- -SupportedCommand
- Script block containing permitted content
Note that the keyword, DATA, is not case sensitive, so, you can use any of data, Data, or, DATA. Within the block one may use two specific elements:
- Any/all PowerShell operators except -match
- Flow control statements: if, else and elseif
- The following automatic variables:
- $PsCulture
- $PsUICulture
- $True
- $False
- $Null
- Comments
- Pipelines
- Statements separated by semicolons
- litearls
- Cmdlets permitted in the data section (default is ConvertFrom-StringData)
- Cmdlets explicitly indicated by -SupportedCommand parameter
The one outstanding, default cmdlet, ConvertFrom-StringData, allows the use of single and double-quoted strings in key/value pairs as well as here strings, also, available in both single and double-quoted string formats.
To demonstrate how these work, here are some examples. In this first example, we will look at a sample data section contained in the same script in which the data is referenced. This is the "all in one" approach.
When I execute this command Powershell converts the key/value pairs in the here string to properties on my $TextMsgs object so I can call the key. In this case, I call the .Text001 property and PowerShell prints out Windows 7.$TextMsgs = DATA {ConvertFrom-StringData-stringdata @Text001 = Windows 7Text002 = Windows Server 2008 R2@}$TextMsgs.Text001
In our second example, we use a simple DATA section with strings (but no keys). In this case, you need to reference the strings using zero-based, array notation, as shown below:
which prints out Thank you for using my Windows PowerShell Organize.pst script. when executed. In essence, you are looking at either dealing with arrays (as in the previous example) or hashtables (the first example). In either case, these are effective ways to get data and code broken out into separate segments.$strings = DATA {"Thank you for using my Windows PowerShell Organize.pst script.""It is provided free of charge to the community.""I appreciate your comments and feedback."}$strings[0]
The second type of use for data sections is with psd1 file (PowerShell data files I presume the acronym to be). To get an idea of where this really fits in, we need to glance briefly at another concept: script internationalization. For enterprise packages, that is PowerShell solutions provided to multiple languages, the code/logic of scripts works the same regardless of the language in which the script is run. What changes, however, are the messages to the end user. While I was originally researching this topic I was looking for ways to handle large text blocks simply and easily. One of the concepts I stumbled upon was the use of locations, otherwise known as code pages, to associate .psd1 file content with scripts.
In my example, I created two files:
- test.ps1
- test.psd1
The first file performs a few, quick steps:
- Clear the host
- Call the Import-LocalizedData cmdlet with two parameters: -FileName (referencing a file in the same directory called test.psd1) and -BindingVariable (with a value of Test).
- Outputs $Test.Values
The second file, test.psd1, is our "data" file. This is the second approach, where data and code are separated into two distinct files, one, the .ps1 file, containing script code, and, the other, the .psd1 file, containing the raw data. In this model, where the data section is in its own file, the key/value pair is stored as shown below:
When I execute the .ps1 script I see the following in the console:ConvertFrom-StringData @name=A long string@
A long stringHeres whats going on:
- The host runs the test.ps1 files first command: Clear-Host. This wipes the host clean.
- Line 2 calls the Import-LocalizedData cmdlet. This references the test.psd1 denoted by the -FileName argument and converts the string into a key/value pair associated with the variable $Test specified as the -BindingVariable argument.
- Control is passed back to the test.ps1 script and Write-Output prints the value of $Test.name, A long string
While my test scenario proves helpful as a proof of concept the real application of this idea comes into play only when you start employing various cultures. For instance, if you are writing scripts for multiple cultures, PowerShell references the code page of your machine, and, utilizes that code pages .psd1 file. This is how the dynamic aspects of the language really take advantage of the concept.
One thing I saw that looked interesting, but, which I didnt take time to test, is the creation of custom cmdlets. For instance, in the help, there is a hypothetical custom cmdlet, Format-Xml, that is referenced in the -SupportedCommand parameter, to utilize a set of strings indicated in the data section of a separate here string. A custom function is more likely a use case I could envision myself taking advantage of, but, even then, I dont have a solid example to illustrate the proper use of -SupportedCommand with the data section concept.
If you want some sample .psd1 files to check out, below is a list of directories which contain CL_LocalizationData.psd1 files on my default Windows 7 Enterprise installation. Most any Windows 7 machine should have similar instances of these files for you to explore:
- C:WindowsdiagnosticssystemPrinteren-US
- C:WindowsdiagnosticssystemSearchen-US
- C:WindowsdiagnosticssystemWindowsMediaPlayerConfigurationen-US
- C:WindowsdiagnosticssystemWindowsMediaPlayerMediaLibraryen-US
- C:WindowsdiagnosticssystemWindowsUpdateen-US
There are plenty of other directories with similar .psd1 files. For instance, tehre are some RS_* file you can check out that have similar sets of single-quoted here strings in the secondary, date files. In both cases, you just need to look around at a few of them. Unfortunately, for script writers, most of these files reference compiled assemblies instead of scripts or script modules. In short, you wont see a lot of on-the-box references to give you sample code for reference materials in the way of .ps1 files.
One thing that is interesting to see is the use of format place holders. The idea here is that you can leave one (or more) placeholders to be dynamically evaluated at run time. If you have a piece of data to be parsed by the script, you simply indicate the place holder with standard format string specifier, "{0}", within a double-quoted here string (to ensure evaluation) and voila, self-updating data sections. Here is an example using a single-page approach (where the data section is defined in the same code as it is used.
When this is run, I get the following output:$data = ConvertFrom-StringData @"HostName=The hostname of this machine is: {0}."@$data.HostName -f $env:COMPUTERNAME
The hostname of this machine is: MYLAPTOP.Again, the mechanics here are:
- The $data object is populated with a key/value pair generated by the ConvertFrom-StringData cmdlet.
- $data.HostName uses the -f (format operator) to pass the evaluated value of $env:COMPUTERNAME to the {0} placeholder.
- {0} is replaced with MYLAPTOP and the entire string is output to the host.