Best practice to use string data from a file - powershell

What is the best practice to create a text-based database for a PowerShell script?
What I really mean exactly?
I have a PowerShell script which use an url. This address may be used in other PS scripts, but I would be pretty happy if a quite simple practice is exists to solve to store this URL (or more) in a file, and the scripts use form this content, what I only have to define, which variable, line should be used, like this:
SharePointUrl = "..."
CcUrl = "..."
And in the script:
$SPUrl = DataFile.SharePointUrl ...
Something like this.

Not sure if I understand your question correctly. Are you looking for something like this?
$SharePointUrl = 'http://www.example.org/...'
New-Object -Type PSObject -Property #{'URL'=$SharePointUrl} |
Export-Csv 'C:\path\to\some.csv' -NoType
$DataFile = Import-Csv 'C:\path\to\some.csv'
$SPUrl = $DataFile.URL
Edit: After re-reading your question it seems you have an input file with key=value pairs. That can be processed like this:
$DataFile = Get-Content 'C:\path\to\data.txt' -Raw | ConvertFrom-StringData
$SPUrl = $DataFile.SharePointUrl
Another option is to write the configuration to a second PowerShell script:
# as variables
$SharePointUrl = "..."
$CcUrl = "..."
# or as a hashtable
$DataFile = #{
SharePointUrl = "..."
CcUrl = "..."
}
and dot-source that script in your original script.
. 'C:\path\to\config.ps1'

Related

Powershell envrionment variable inheritance

guys..i'm not familiar with PS and at beginning it was a simple question,but it's not so simple for me. (Sorry)
I need to take one environment variable as a string to another env. variable
something like this sketch "$env:$env:var"
I've tried (of course not working,but helps to understand):
$env:admin1 = "123456"
$env:user = "admin1"
$password = $env:"${env:user}"
Write-Host $password
Is it possible in powershell?
Thank you!
It's not possible the way you're doing it, but you can query the environment and match the variable name from a variable a number of different ways. Here's one:
$appData = 'APPDATA'
$value = (Get-ChildItem Env: | ? { $_.Name -eq $appData }).Value
$value # Output

How do you properly create objects with functions in Powershell?

We have a Powershell script which loads some configuration for our application from a json file via something like:
$ourApplicationSettings=Get-Content -Raw -Path $EnvironmentFile | ConvertFrom-Json
Now that I have an object which contains all of the settings, I'd like to create a handful of functions which can operate on either the $settings object directly, or some portion of it.
The best practices articles I've read for Powershell state that functions should be of the form: Verb-Noun, which sounds like developers would be expected to write functions like:
Get-OurAppSourceDirectory $ourApplicationSettings
DoSomething-OurApp $ourApplicationSettings
This seems very counter intuitive as it means that there is no way to easily find all of the functions associated with OurApp.
One article proposes one possible way would be to use a function like:
function New-OurAppConfig {
$appConfig = Get-Content -Raw -Path $EnvironmentFile | ConvertFrom-Json
$appConfig
}
but this way I'm not sure how to add member functions so that I could write:
$config = New-OurAppConfig
$config.Get-SrcDirectory
$config.Invoke-ActionABC
Well, you can search for that. Get-Command *ourapp*. So that one is not true. As for the second question:
$config = New-OutAppConfig
$var = Get-SrcDirectory $config
Invoke-ActionABC $var
or alternatively you can do a meta function that calls all those functions, so you could just call it once and thats it.
Also, it seems like what you are trying to achieve can be done with classes in PowerShell. https://blogs.technet.microsoft.com/heyscriptingguy/2015/09/04/adding-methods-to-a-powershell-5-class/
You can use Add-Member to add script methods to an existing object:
$foo = '{"value":23}' | ConvertFrom-Json
$foo | Add-Member -Type ScriptMethod -Name Multiply -Value {
Param($factor)
$this.value * $factor
}
$foo.Multiply(2) # output: 46
$foo.Multiply(3) # output: 69
That approach is a little awkward, though, both because PowerShell prior to v5 wasn't really built for full OO and because calling methods on a configuration object feels odd.
Generally it's more PoSh to read your configuration once at the beginning of your script and then either use it as a global singleton:
$cfg = Get-Content 'C:\path\to\your.json' -Raw | ConvertFrom-Json
function Get-Something {
...
Invoke-Other $cfg.Whatever
...
}
Get-Something
or make the configuration a mandatory parameter for your functions:
$cfg = Get-Content 'C:\path\to\your.json' -Raw | ConvertFrom-Json
function Get-Something {
Param(
[Parameter(Mandatory=$true)
$Config
)
...
Invoke-Other $Config.Whatever
...
}
Get-Something -Config $cfg

Powershell to read some strings from each line

I have a requirement like:
Have a text file containing the following in the following pattern
172.26.xxy.zxy:Administrator:Password
172.26.xxy.yyx:Administrator:Password
172.26.xxy.yyy:Administrator:Password
172.26.xxy.yxy:Administrator:Password
I need my powershell script to read each word and use that word whereever required. For example,
foreach(something)
{
I want the IP's(172.26.---.---) to read and store the value as a variable.
I want to store the two words after **:** in seperate variables.
}
How can this be done? I know to read an entire file or get some specific string. But I need the same to be done on each line.Any help would be really appreciated.
Something like this? You can just split on the : and then store your variables based on the index
$contents = Get-Content C:\your\file.txt
foreach($line in $contents) {
$s = $line -split ':'
$ip = $s[0]
$user = $s[1]
$pass = $s[2]
write-host $ip $user $pass
}
minor edit: "t" missing in content.
You can write a regular expression to replace to remove the parts you do not need
$ip_address= '172.26.xxy.zxy:Administrator:Password' -replace '^(.+):(.+):(.+)$','$1'
$user= '172.26.xxy.zxy:Administrator:Password' -replace '^(.+):(.+):(.+)$','$2'
$pwd= '172.26.xxy.zxy:Administrator:Password' -replace '^(.+):(.+):(.+)$','$3'
I think the more generic and pure Powershell way would be something like this:
Select-String "(.*):(.*):(.*)" c:\file.txt |
Select #{Name="IP"; Expression = {$_.Matches.Groups[1]}},
#{Name="User"; Expression = {$_.Matches.Groups[2]}},
#{Name="Password"; Expression = {$_.Matches.Groups[3]}}
The Output would be then an array of objects each having three properties IP, User and Password. So you can now use them for your purposes, or just add more commands at the end of the pipe.

Powershell: Force variable expansion

How can I force string variable expansion?
I need to read a string with one or more variable names in it (a template) and then expand it after I read the file. The key is that I must read the contents of the file before I declare the variables that will be used in the expansion. I've tried several ways but I can't get it to work.
It's not an option to read the file after $environment is defined.
Contents of name.txt:
$environment-RPT-INT
#example 1
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
#example 2
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $expanded = Invoke-Expression "`"$template`""
$expanded
#example 3
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$name = $name.Clone()
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
Any help is appreciated.
Updated: Example 1 is now working for me.
It looks like you've found some possible solutions, but I'll suggest another that is in my opinion a bit smarter and more robust.
Instead of requiring variable names in your text file, why not use format specifiers. For example, the contents of name.txt:
{0}-RPT-INT
And in your script:
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $name -f $environment
$expanded
This way, you can rename the variable w/o changing any of your text files. As a bonus, if your text file comes from unknown sources, your script is vulnerable to code injection. For example, say you are given a text file with these contents:
$(rm -whatif -recurse -force c:\)-RPT-INT
Then ExpandString or Invoke-Expression will happily execute that code.
Your Invoke-Expression example is pretty close. Instead of using $template though, you need to use $name.
#example 2
$name = gc 'c:\temp\name.txt';
$environment = '9065DEV';
$expanded = Invoke-Expression -Command "`"$name`"";
$expanded;
If you are willing to store your Setting/Values in a CSV, I wrote a module to pull values from a CSV, put it into a HereString... any variables you put into the CSV become fully expanded inside the Here-String. That way, you can normally address the field names and values.
I might be able to change this to also work with traditional INI's if anyone is interested.
https://github.com/Inventologist?tab=repositories
Look for: HereStringFromCSV
There is a function called ExpandString predefined in powershell. It's inside $ExecutionContext, as follows.
$mystring = #'
This is a here string with some embedded variables.
Here is variable foo -- $foo
Here is variable bar -- $bar
Here is variable bletch -- $bletch
'#
#This displays the here string as is.
$mystring
#now define foo, bar and bletch
$foo = 5
$bar = Get-Date
$bletch = "George Washington"
#now run the here string through Expandstring.
$ExecutionContext.InvokeCommand.ExpandString($mystring)

Assigning proper variable types when read from a config file [PowerShell]

I am trying to read values from a text file and keep them as variables to use in my script.
This config file contains strings, ints, booleans and an array that can contain strings, ints and booleans.
When I declare the variables outright, I have no problems. My script functions as expected. However when I am reading in the config file and trying to create variables based on that, I only get the variables declared as strings.
This creates my config file in the format I would like.
Function Create-Config() {
If (!(Test-Path config.txt)) {
$currentlocation=Get-Location
$parentfolder=(get-item $currentlocation).parent.FullName
New-Item config.txt -ItemType "file"
Add-Content config.txt "SERVER_NAME=MyServer"
Add-Content config.txt "SERVER_LOCATION=$currentlocation"
Add-Content config.txt "BACKUP_LOCATION=$parentfolder\backup"
Add-Content config.txt "CRAFTBUKKIT=craftbukkit.jar"
Add-Content config.txt "JAVA_FLAGS=-Xmx1G"
Add-Content config.txt "CRAFTBUKKIT_OPTIONS=-o True -p 1337"
Add-Content config.txt "TEST_DEPENDENCIES=True"
Add-Content config.txt "DELETE_LOG=True"
Add-Content config.txt "TAKE_BACKUP=True"
Add-Content config.txt "RESTART_PAUSE=5"
}
}
However, either I need to change how I create my config file, or change how I import those variables. I want the config file to be as simple as possible. I am using this code to import the values:
Function Load-Variables() {
Get-Content config.txt | Foreach-Object {
$var = $_.Split('=')
New-Variable -Name $var[0] -Scope Script -Value $var[1]
}
}
As you can see, I don't explicitly set the variable, since the variables from the config are different types (booleans, int, array, strings). However, PowerShell imports these all as strings. I can import all variables individually (which I may have to do) but I'm still feeling like I will be stuck on the array.
If I declare the array using this command:
New-Variable -Name CRAFTBUKKIT_OPTIONS -Option Constant -Value ([array]#('-o',$true,'-p',25565))
I get exactly what I want, but I need to import it from the config file instead of declaring the variable in my script. The java program is a bit finicky, so I cannot just import that value as a string, or it will not get passed properly and those options get ignored. I've found the only way it works is to have it as an array (as defined above). I also want to note that there could be many more config file options presented than in my example.
I am not sure what is the better approach - importing the variables to be declared correctly (what I would like to do), or assuming they cannot be imported as anything other than a string and then parsing that string into the proper variable types after.
I have tried declaring the variables before hand and using the Set-Variable command to set the values, but that doesn't work. It very much seems like my variables are being imported with Get-Content as strings from the start instead of the correct types.
Full script is here:
https://gist.github.com/TnTBass/4692f2a00fade7887ce4
Any help?
$types = #{
SERVER_NAME = {$args[0]}
SERVER_LOCATION = {$args[0]}
BACKUP_LOCATION = {$args[0]}
CRAFTBUKKIT = {$args[0]}
JAVA_FLAGS = {$args[0]}
CRAFTBUKKIT_OPTIONS = { ($args[0].split(' ')[0] -as [string]),
([bool]::Parse($args[0].split(' ')[1])),
($args[0].split(' ')[2] -as [string]),
($args[0].split(' ')[3] -as [int]) }
TEST_DEPENDENCIES = {[bool]::Parse($args[0])}
DELETE_LOG = {[bool]::Parse($args[0])}
TAKE_BACKUP = {[bool]::Parse($args[0])}
RESTART_PAUSE = {$args[0] -as [int]}
}
$ht = [ordered]#{}
gc config.txt |
foreach {
$parts = $_.split('=').trim()
$ht[$parts[0]] = &$types[$parts[0]] $parts[1]
}
New-object PSObject -Property $ht
SERVER_NAME : MyServer
SERVER_LOCATION : C:\testfiles
BACKUP_LOCATION : C:\\backup
CRAFTBUKKIT : craftbukkit.jar
JAVA_FLAGS : -Xmx1G
CRAFTBUKKIT_OPTIONS : {-o, True, -p, 1337}
TEST_DEPENDENCIES : True
DELETE_LOG : True
TAKE_BACKUP : True
RESTART_PAUSE : 5
The $types hash table uses parameter names from your configuration file for the keys, and script blocks that define the typing and data transformation that needs to be done on the string value for that parameter you're reading from the file. As each line is read in from the file, this part of the script:
$parts = $_.split('=').trim()
$ht[$parts[0]] = &$types[$parts[0]] $parts[1]
Splits it at the '=', then looks up the script block for that parameter and invokes it using the value as it's argument. The results are stored in a hash table ($ht), and then that's used to create an object. You can omit the object creation and just use the hash table to pass your config values if that's more appropriate for your application.
You might need to add some error trapping to test the input data and/or resulting values for production work. but I think the hash table of script blocks is a pretty clean way doing to present the typing and transformation, and should be fairly intuitive to read and easy to maintain in the script if you need to make changes. The first 5 parameters are string parameters, and are just returned as-is, but you can explicit cast them as [string] in the script block just for clarity.
Of course Powershell handles the variable values as strings. That's because it cannot tell string "1337" apart from integer 1337 without some extra help. In order to specify the data type, you need some metadata. There is an format just for that - XML. Now, you don't need to create an XML file by yourself. There are cmdlets Import-CliXML and Export-CliXML that manage Powershell object serialization.
One could for example save the configuration settings in a hash table and serialize it like so,
$cfgSettings = #{
"currentlocation" = "my current location";
"parentfolder" = "my backup location";
"SERVER_NAME" = "MyServer";
"SERVER_LOCATION" = $currentlocation;
"BACKUP_LOCATION" = "$parentfolder\backup";
"CRAFTBUKKIT" = "craftbukkit.jar";
"JAVA_FLAGS" = "-Xmx1G";
"CRAFTBUKKIT_OPTIONS" = "-o True -p 1337";
"TEST_DEPENDENCIES" = $true;
"DELETE_LOG" = $true;
"TAKE_BACKUP" = $true;
"RESTART_PAUSE" = 5
}
Export-Clixml -Path myConf.xml -InputObject $cfgSettings
The file contains serialized hashtable with data types. For example, DELETE_LOG is a boolean, RESTART_PAUSE an int and so on:
<En>
<S N="Key">DELETE_LOG</S>
<B N="Value">true</B>
</En>
<En>
<S N="Key">RESTART_PAUSE</S>
<I32 N="Value">5</I32>
</En>
<En>
<S N="Key">JAVA_FLAGS</S>
<S N="Value">-Xmx1G</S>
</En>
Repopulating and accessing the settings hashtable is not hard either:
$config = Import-CliXML myConf.xml
$config["DELETE_LOG"] # NB! Case sensitive, "delete_log" is different a key!
True
Edit
As per how to create the array, here is a sample that uses deserialized data.
Split the options and serialize the values:
$config = #{
"CRAFTBUKKIT_OPTION1" = "-o" ;
"CRAFTBUKKIT_OPTION2" = $true ;
"CRAFTBUKKIT_OPTION3" = "-p" ;
"CRAFTBUKKIT_OPTION4" = 1337 }
Export-Clixml -InputObject $config -Path .\temp\conf.xml
Deserialize the values and create an array out of them:
$config2 = Import-Clixml C:\temp\conf.xml
$array = #(
$config2["CRAFTBUKKIT_OPTION1"],
$config2["CRAFTBUKKIT_OPTION2"],
$config2["CRAFTBUKKIT_OPTION3"],
$config2["CRAFTBUKKIT_OPTION4"])
Print the array contents with type info:
$array | % { $("{0} {1}" -f $_, ($_.gettype().name)) }
# Output
-o String
True Boolean
-p String
1337 Int32