How to Create Text Files from an Array of Values in Powershell - arrays

I have a text file "list.txt" with a list of hundreds of URL's that I want to parse, along with some common-to-all config data, into individual xml files (config files) using each value in "list.txt", like so:
list.txt contains:
line_1
line_2
line_3
The boilerplate config data looks like (using line_1 as an example):
<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Url>line_1.mydomain.com</Url>
<Title>line_1</Title>
<Enabled>true</Enabled>
<PluginInName>Tumblr</PluginInName>
</Website>
So if "list.txt" contains 100 items, I want 100 config files written with the URL and Title elements individualized.
I have fumbled with several posts on reading the array and on creating text files, but I haven't been able to make any of it work.
What I tried, although it's munged at this point. I'm not sure where I started or how I got to here:
$FileName = "C:\temp\list.txt"
$FileOriginal = Get-Content $FileName
# create an empty array
Foreach ($Line in $FileOriginal)
{
$FileModified += $Line
if ($Line -match $pattern)
{
# Add Lines after the selected pattern
$FileModified += 'add text'
$FileModified += 'add second line text'
}
}
Set-Content $fileName $FileModified
This is way beyond my neophyte Powershell skills. Can anyone help out?

You're looking for a string-templating approach, where a string template that references a variable is instantiated on demand with the then-current variable value:
# Define the XML file content as a *template* string literal
# with - unexpanded - references to variable ${line}
# (The {...}, though not strictly necessary here,
# clearly delineates the variable name.)
$template = #'
<code>
<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Url>${line}.mydomain.com</Url>
<Title>${line}</Title>
<Enabled>true</Enabled>
<PluginInName>Tumblr</PluginInName>
</Website>
'#
# Loop over all input lines.
Get-Content C:\temp\list.txt | ForEach-Object {
$line = $_ # store the line at hand in $line.
# Expand the template based on the current $line value.
$configFileContent = $ExecutionContext.InvokeCommand.ExpandString($template)
# Save the expanded template to an XML file.
$configFileContent | Set-Content -Encoding Utf8 "$line.xml"
}
Notes:
I've chosen UTF-8 encoding for the output XML files, and to name them "$line.xml", i.e. to name them for each input line and to store them in the current location; adjust as needed.
The template expansion (interpolation) is performed via automatic variable $ExecutionContext, whose .InvokeCommand property provides access to the .ExpandString() method, which allows performing string expansion (interpolation) on demand, as if the input string were a double-quoted string - see this answer for a detailed example.
Surfacing the functionality of the $ExecutionContext.InvokeCommand.ExpandString() method in a more discoverable way via an Expand-String cmdlet is the subject of this GitHub feature request.
Ansgar Wiechers points out that a simpler alternative in this simple case - given that only a single piece of information is passed during template expansion - is to use PowerShell's string-formatting operator, -f to fill in the template:
# Define the XML file content as a *template* string literal
# with '{0}' as the placeholder for the line variable, to
# be instantiated via -f later.
$template = #'
<code>
<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Url>{0}.mydomain.com</Url>
<Title>{0}</Title>
<Enabled>true</Enabled>
<PluginInName>Tumblr</PluginInName>
</Website>
'#
# Loop over all input lines.
Get-Content C:\temp\list.txt | ForEach-Object {
# Expand the template based on the current $line value.
$configFileContent = $template -f $_
# Save the expanded template to an XML file.
$configFileContent | Set-Content -Encoding Utf8 "$line.xml"
}
Optional reading: choosing between -f and $ExecutionContext.InvokeCommand.ExpandString() for template expansion:
Tip of the hat to Ansgar for his help.
Using -f:
Advantages:
It is made explicit on invocation what values will be filled in.
Additionally, it's easier to include formatting instructions in placeholders (e.g., {0:N2} to format numbers with 2 decimal places).
Passing the values explicitly allows easy reuse of a template in different scopes.
An error will occur by default if you accidentally pass too few or too many values.
Disadvantages:
-f placeholders are invariably positional and abstract; e.g., {2} simply tells you that you're dealing with the 3rd placeholder, but tells you nothing about its purpose; in larger templates with multiple placeholders, this can become an issue.
Even if you pass the right number of values, they may be in the wrong order, which can lead to subtle bugs.
Using $ExecutionContext.InvokeCommand.ExpandString():
Advantages:
If your variables have descriptive names, your template will be more readable, because the placeholders - the variable names - will indicate their purpose.
No need to pass values explicitly on invocation - the expansion simply relies on the variables available in the current scope.
Disadvantages:
If you use a template in multiple functions (scopes), you need to make sure that the variables referenced in the template are set in each.
At least by default, $ExecutionContext.InvokeCommand.ExpandString() will quietly ignore nonexistent variables referenced in the template - which may or may not be desired.
However, you can use Set-StrictMode -Version 2 or higher to report an error instead; using Set-StrictMode is good practice in general, though note that its effect isn't lexically scoped and it can disable convenient functionality.
Generally, you manually need to keep your template in sync with the code that sets the variables referenced in the template, to ensure that the right values will be filled in (e.g., if the name of a referenced variable changes, the template string must be updated too).

Related

How do I sort an array of objects by one of their property values in Powershell?

For example, I have a variable, which returns the line with several arrays:
#{sourceDSAcn=B; LastSyncResult=0} #{sourceDSAcn=A; LastSyncResult=9} #{sourceDSAcn=C; LastSyncResult=0} #{sourceDSAcn=M; Last SyncResult=10}
I want to sort this line alphabetically by one of parameters. In this case - by sourceDSAcn, so result must be like that:
#{sourceDSAcn=A; LastSyncResult=9} #{sourceDSAcn=B; LastSyncResult=0} #{sourceDSAcn=C; LastSyncResult=0} #{sourceDSAcn=M; Last SyncResult=10}
How can I do that?
Your output format suggests two things:
The objects aren't arrays, but custom objects ([pscustomobject] instances).
You've used the Write-Host cmdet to print these objects to the host (display), which results in the hashtable-literal-like representation shown in your question (see this answer).
If, instead, you want the usual rich display formatting you get by default - while still sending the output to the host only rather than to the success output stream - you can use the Out-Host cmdlet.
Conversely, to produce data output to the pipeline, use either the Write-Output cmdlet or, preferably, PowerShell's implicit output feature, as shown below; for more information, see this answer.
In order to sort (custom) objects by a given property, simply pass the name of that property to
Sort-Object's (positionally implied) -Property parameter, as Mathias R. Jessen helpfully suggests:
# Using $variable by itself implicitly sends its value through the pipeline.
# It is equivalent to: Write-Output $variable | ...
$variable | Sort-Object sourceDSAcn # same as: ... | Sort-Object -Property sourceDSAcn

How to have a Powershell validatescript parameter pulling from an array?

I have a module with a lot of advanced functions.
I need to use a long list of ValidateSet parameters.
I would like to put the whole list of possible parameters in an array and then use that array in the functions themselves.
How can I pull the list of the whole set from an array?
New-Variable -Name vars3 -Option Constant -Value #("Banana","Apple","PineApple")
function TEST123 {
param ([ValidateScript({$vars3})]
$Fruit)
Write-Host "$Fruit"
}
The problem is that when I use the function it doesn't pull the content from the constant.
TEST123 -Fruit
If I specify the indexed value of the constant then it works.
TEST123 -Fruit $vars3[1]
It returns Apple.
You are misunderstanding how ValidateScript ...
ValidateScript Validation Attribute
The ValidateScript attribute specifies a script that is used to
validate a parameter or variable value. PowerShell pipes the value to
the script, and generates an error if the script returns $false or if
the script throws an exception.
When you use the ValidateScript attribute, the value that is being
validated is mapped to the $_ variable. You can use the $_ variable to refer to the value in the script.
... works. As the others have pointed out thus far. You are not using a script you are using a static variable.
To get what I believe you are after, you would do it, this way.
(Note, that Write- is also not needed, since output to the screen is the default in PowerShell. Even so, avoid using Write-Host, except for in targeted scenarios, like using color screen output. Yet, even then, you don't need it for that either. There are several cmdlets that can be used, and ways of getting color with more flexibility. See these listed MS powershelgallery.com modules)*
Find-Module -Name '*Color*'
Tweaking your code you posted, and incorporating what Ansgar Wiechers, is showing you.
$ValidateSet = #('Banana','Apple','PineApple') # (Get-Content -Path 'E:\Temp\FruitValidationSet.txt')
function Test-LongValidateSet
{
[CmdletBinding()]
[Alias('tlfvs')]
Param
(
[Validatescript({
if ($ValidateSet -contains $PSItem) {$true}
else { throw $ValidateSet}})]
[String]$Fruit
)
"The selected fruit was: $Fruit"
}
# Results - will provide intellisense for the target $ValidateSet
Test-LongValidateSet -Fruit Apple
Test-LongValidateSet -Fruit Dog
# Results
The selected fruit was: Apple
# and on failure, spot that list out. So, you'll want to decide how to handle that
Test-LongValidateSet -Fruit Dog
Test-LongValidateSet : Cannot validate argument on parameter 'Fruit'. Banana Apple PineApple
At line:1 char:29
Just add to the text array / file but this also means, that file has to be on every host you use this code on or at least be able to reach a UNC share to get to it.
Now, you can use the other documented "dynamic parameter validate set". the Lee_Daily points you to lookup, but that is a bit longer in the tooth to get going.
Example:
function Test-LongValidateSet
{
[CmdletBinding()]
[Alias('tlfvs')]
Param
(
# Any other parameters can go here
)
DynamicParam
{
# Set the dynamic parameters' name
$ParameterName = 'Fruit'
# Create the dictionary
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Create the collection of attributes
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
# Create and set the parameters' attributes
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $true
$ParameterAttribute.Position = 1
# Add the attributes to the attributes collection
$AttributeCollection.Add($ParameterAttribute)
# Generate and set the ValidateSet
$arrSet = Get-Content -Path 'E:\Temp\FruitValidationSet.txt'
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
# Add the ValidateSet to the attributes collection
$AttributeCollection.Add($ValidateSetAttribute)
# Create and return the dynamic parameter
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
return $RuntimeParameterDictionary
}
begin
{
# Bind the parameter to a friendly variable
$Fruit = $PsBoundParameters[$ParameterName]
}
process
{
# Your code goes here
$Fruit
}
}
# Results - provide intellisense for the target $arrSet
Test-LongValidateSet -Fruit Banana
Test-LongValidateSet -Fruit Cat
# Results
Test-LongValidateSet -Fruit Banana
Banana
Test-LongValidateSet -Fruit Cat
Test-LongValidateSet : Cannot validate argument on parameter 'Fruit'. The argument "Cat" does not belong to the set "Banana,Apple,PineApple"
specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
At line:1 char:29
Again, just add to the text to the file, and again, this also means, that file has to be on every host you use this code on or at least be able to reach a UNC share to get to it.
I am not sure exactly what your use case is, but another possibility if you're using PowerShell 5.x, or newer, is to create a class, or if you're using an older version you could embed a little C# in your code to create an Enum that you can use:
Add-Type -TypeDefinition #"
public enum Fruit
{
Strawberry,
Orange,
Apple,
Pineapple,
Kiwi,
Blueberry,
Raspberry
}
"#
Function TestMe {
Param(
[Fruit]$Fruit
)
Write-Output $Fruit
}

filling attribute with concatenated string

I am looking for a way to concatenate a string and put it in one active directory user account object, to be precise, in altsecurityidentities.
The value to be input will be as following:
" which is constant, and (firstName)(whitespace)(lastname) (custom value which can be taken from another attribute, which is in form x.yyyyyyyy.z (what matters to me is yyyyyyy part (.substring(2,8)) works like charm here.
I'd like to do it for several accounts that are listed in variable of type TypeName: Microsoft.ActiveDirectory.Management.ADUser.
So that it can be set for all accounts under $accounts variable.
So far I have the code to create the attribute value for one account listed in there:
$accounts | %{'constant value'+$.givenname+' '+$.surname+' '+'('+$(($_.attributename).substring(2,8))+')'}
This, i'd like to put in altsecurityidentities attribute value, log to event viewer success and errors
You're almost there really just need to apply the value to the desired field:
$accounts | ForEach-Object { Set-ADUser -Identity $_ -Add #{altsecurityidentities = "constant value $($_.givenname) $($_.surname) ($($_.attributename.substring(2,8)))"} }
I have tidied up your code by embedding the variables in a string rather than using concatenation, which is much cleaner.

Clojure, raynes.fs, iterate-dir: a cryptic file-related data structure

I'm trying to traverse a filesystem tree using iterate-dir function from raynes.fs library: https://github.com/Raynes/fs/blob/master/src/me/raynes/fs.clj
Everything makes sense, except for the "File" part of the returned sequence of vectors (I tried to catch output from the clipboard and define a variable of it for research purposes):
(def res ([#<File /home/alexey/dir-src> #{"3" "4" "1" "2" "10"} #{"Фото-0015.jpg"}]
[#<File /home/alexey/dir-src/1> #{} #{"vB8vqyc4XBk.jpg" "valet.jpg"}]
[#<File /home/alexey/dir-src/10> #{} #{"jca3.jpg" "jca10.jpg" "jca1.jpg" "jca4.jpg" "jca2.jpg"}]
[#<File /home/alexey/dir-src/2> #{"002"} #{"warrior-babe-305079.jpg" "wallp_fant_0017.jpg"}]
[#<File /home/alexey/dir-src/2/002> #{} #{"tumblr_mt7rckyTbi1qd5ic3o1_500.jpg"}]
[#<File /home/alexey/dir-src/3> #{} #{"Сияние-cosplay-931717.jpeg"}]
[#<File /home/alexey/dir-src/4> #{} #{}]))
clojure.lang.ExceptionInfo: Unreadable form :: {:column 14, :line 27, :type :reader-exception}
The error also makes perfect sense, as there's no data structure literals like #< ... > in Clojure. I do need the paths for mapping and sorting, but I have no idea how to access them. By the way, the standard compare function gets accepted:
(sort compare (fs/iterate-dir path))
the sequence above is sorted, though not quite the way I want it.
The File objects are standard java.io.File instances.
You can look up java.io.File in javadoc to find the operations that can be done on a File.
Like most java classes, it has no Clojure read support, so it must be created via constructor calls (ie. (java.io.File. ".") to get a File for the current directory).
As the doc for iterate-dir mentions, it will return a sequence of every node in the tree starting with the input. You can ignore the File if it does not have anything you want in it. Likely, at each node you would want to know the path to that node though.
user> (java.io.File. ".")
#<File .>
user> (.getCanonicalPath (java.io.File. "."))
"/home/noisesmith/example/"

How to create an array to hold the values of several automatically generated checkboxes in Tk Tcl

I have a list of objects of unknown length, I want to create 2 lists of checkboxes, in each list a checkbox for each line of the list. I also need to be able to get to the checkboxes and check their value.
Since I didn't find a way to get the value of a checkbox from its path, I wanted to create a global array that will hold the variables for the check boxes, and tried the fallowing:
global cb
set i 0
foreach port $ports {
set to_cb [checkbutton $to.cb_to_$i -variable [list $cb(to$i)] -text $port -command [list __sp_from_to_changed $from $to]]
set from_cb [checkbutton $from.cb_from_$i -variable [list $cb(from$i)] -text $port -command [list __sp_from_to_changed $from $to]]
grid $to_cb -row [expr $i + 2] -sticky nsew
grid $from_cb -row [expr $i + 2] -sticky nsew
incr i
When I source it get the error can't read "cb(to0)": no such variable, how can I assign a variable inside an array for a check box, or better yet, is there a way to get the current value directly from the checkbox without using intermediate variables?
Checkbuttons always need to be bound to a variable (there is a default, but you don't want to use that). What you need to do is to generate the name of a variable that is unique for each checkbutton; an array element is an excellent choice here.
So what's the problem in your code? It's simply that you're trying to dereference the variables by putting a $ in front of them; unlike some other languages, Tcl always uses the $ to mean “read this variable” (except in regular expressions). You also don't need to wrap the variable name in list; that would only be useful if you putting it into a generated script. Instead, you need lines like this:
set to_cb [checkbutton $to.cb_to_$i -variable cb(to$i) -text $port \
-command [list __sp_from_to_changed to $i]]
set from_cb [checkbutton $from.cb_from_$i -variable cb(from$i) -text $port \
-command [list __sp_from_to_changed from $i]]
The changes here are:
Removed the extra generated complexity from the -variable option's value; a bare array element name is good enough.
Updated the arguments to __sp_from_to_changed so that they say what has actually been modified (the first argument is now to or from, and the second is the index; from that, you should be able to construct all the element names you need quite trivially).
Added a backslash to those long lines so I could break them up for readability. :-)

Resources