I have the following .ini file (named metrics.ini) containing a single record, but it may have more records added to it in the future:
$DatabaseConnection_STR=MYSERVER\MYINSTANCE
I need to parse this file into a PowerShell variable. I can parse the string with the following, but I am at a loss for creating the new $DatabaseConnection_STR variable (based on what was parsed from the .ini file). I don't want to hardcode $DatabaseConnection_STR in my script--I would rather let the script figure it out so that is can handle additional variables in the future.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(0, $Pos)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information--**the next line fails**
`$Variable_NME=$VariableValue_STR
}
$InputFile.Close()
Any ideas?
It's probably easier and more concise to use the split command. You could also store your configuration values in a hash table:
$config = #{}
Get-Content $IniFile_NME | foreach {
$line = $_.split("=")
$config.($line[0]) = $line[1]
}
You could still use the same method of creating variables if don't want a hash table, but using Powershell's reading, looping, and splitting will make it easier.
This works. It turns out that the New-Variable command does not use a dollar sign ($) with its "-name" parameter; so, I had to parse that out. See below.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(1, $Pos -1)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information
new-variable -name $Variable_NME -value $VariableValue_STR
get-variable -name $Variable_NME
}
$InputFile.Close()
Related
I have 400+ .vcf files that I would like to replace the "FN:" line (line 4) with the file name. I've looked at multiple solutions and I can't seem to find something that will achieve what I'm looking for even though I know there's a way to do this.
This is what I have currently
File Name: LastNamefirstName
BEGIN:VCARD
VERSION:3.0
N:lastName;firstName;;;
FN:firstName lastName
ADR:;;111 Main Rd;Columbia;MO;65202;
TEL;TYPE=mobile:(111) 222-3333
EMAIL;TYPE=work:email#gmail.com
BDAY:20000101
END:VCARD
This is what I would like to achieve
Keep "FN:" and replace the text after it with the file name text.
BEGIN:VCARD
VERSION:3.0
N:lastName;firstName;;;
FN:LastNamefirstName
ADR:;;111 Main Rd;Columbia;MO;65202;
TEL;TYPE=mobile:(111) 222-3333
EMAIL;TYPE=work:email#gmail.com
BDAY:20000101
END:VCARD
This Powershell script does do half what I want but I would really like to take the file name and input it in the replacementLineText.
# Set by user to their needs.
$filesToCheck = "C:\path\*.vcf"
$lineToChange = 4
$replacementLineText = "New Text"
# Gather list of files based on the path (and mask) provided by user.
$files = gci $filesToCheck
# Iterate over each file.
foreach ($file in $files) {
# Load the contents of the current file.
$contents = Get-Content $file
# Iterate over each line in the current file.
for ($i = 0; $i -le ($contents.Length - 1); $i++) {
# Are we on the line that the user wants to replace?
if ($i -eq ($lineToChange - 1)) {
# Replace the line with the Replacement Line Text.
$contents[$i] = $replacementLineText
# Save changed content back to file.
Set-Content $file $contents
}
}
}
Any input or guidance would be greatly appreciated!
I would really like to take the file name and input it in the replacementLineText.
To accept the paths of all target files, all you need to do is declare a parameter:
param(
[Parameter(Mandatory = $true)]
[string[]]$Path
)
$lineToChange = 4
# Gather list of files based on the path (and mask) provided by user.
$files = gci -Path $Path
# ... rest of original script
I made a slight modification to the variable names - Path is the idiomatic parameter name for strings describing expandable paths, and parameter names are generally expected to be upper case.
The Mandatory flag in the [Parameter()] attribute associated with $Path means that the caller MUST supply a value - otherwise PowerShell will prompt for it:
PS C:\> .\script.ps1
cmdlet script.ps1 at command pipeline position 1
Supply values for the following parameters:
Path:
PS C:\> .\script.ps1 -Path "C:\path\*.vcf" # now it won't prompt
For more information on parameters, see the about_Functions and about_Functions_Advanced_Parameters help topics - although the documentation is about functions, the rules for parameters and their declaration is the same for script files (you can think of a script file as a function that happens to sit on the filesystem instead of in memory)
The gci (or Get-ChildItem) cmdlet returns [FileInfo] objects, with all the files metadata, so to use the file name as the replacement value inside the loop, you simply do $file.Name:
$contents[$i] = "FN:$($file.Name)"
# or using the -f format operator:
$contents[$i] = "FN:{0}" -f $file.Name
Since you already know which index (line number minus 1) you want to modify, you can skip the inner loop and instead do:
param(
[Parameter(Mandatory = $true)]
[string[]]$Path
)
$lineToChange = 4
# Gather list of files based on the path (and mask) provided by user.
$files = Get-ChildItem -Path $Path
# Iterate over each file.
foreach ($file in $files) {
# Load the contents of the current file.
$contents = Get-Content $file
if($contents.Count -ge $lineToChange){
# Replace the line with the Replacement Line Text.
$contents[$lineToChange - 1] = "FN:$($file.Name)"
# Save changed content back to file.
Set-Content $file $contents
}
}
I need to update ini configuration file. I managed to convert the file to hastable and updating values. But when I check if the changes are correct in the file, it hasn't changed. Add-Content doesn't work. do I need to convert to String to use Add-Content function?
Configuration file is filled with plain text also.
"ini" Configuration file:
[sqlScript1Deployment]
sqlServerName = '??????????'
olapServerName = '??????????'
(...)
My ps1 code:
[hashtable]$ht = Get-Configuration($iniFilepath)
$ht["sqlScript1Deployment"]["sqlServerName"] = 'Master'
$ht | Add-Content $iniFilepath
Expected code in "ini" file:
[sqlScript1Deployment]
sqlServerName = 'Master'
Actual result in "ini" file:
[sqlScript1Deployment]
sqlServerName = '??????????'
I have no idea where you got the Get-Configuration function from, but if it creates a hashtable where each Key is a Section for the INI and every Value is a name/value pair like this:
$ht = #{
'sqlScript1Deployment' = #{
'sqlServerName' = '??????????'
'olapServerName' = '??????????'
}
}
The following code may help:
# set the new value for sqlServerName
$ht['sqlScript1Deployment']['sqlServerName'] = 'Master'
# write the Hashtable back to disk as .INI file
$sb = New-Object -TypeName System.Text.StringBuilder
# the Keys are the Sections in the Ini file
# the properties are name/value pairs within these keys
foreach ($section in $ht.Keys) {
[void]$sb.AppendLine("[$section]")
foreach ($name in $ht[$section].Keys) {
$value = $ht[$section][$name]
# the value needs to be quoted when:
# - it begins or ends with whitespace characters
# - it contains single or double quote characters
# - it contains possible comment characters ('#' or ';')
if ($value -match '^\s+|[#;"'']|\s+$') {
# escape quotes inside the value and surround the value with double quote marks
$value = '"' + ($value -replace '(["''])', '\$1') + '"'
}
[void]$sb.AppendLine("$name = $value")
}
}
$sb.ToString() | Out-File $iniFilepath
[void]$sb.Clear()
The resulting file will look like this:
[sqlScript1Deployment]
sqlServerName = Master
olapServerName = ??????????
I am trying to read a config file using PowerShell and then convert it to hashtable so that later I can retrieve the value by key.
Here is the code:
$installerConfigs = Get-Content $configFilePath | ConvertFrom-StringData
$installerConfigs
foreach ($h in $installerConfigs.GetEnumerator()) {
Write-Host "$($h.Name): $($h.Value)"
}
While the content of the config file is
admin_username = 'administrator'
admin_password = '123456'
when I print the hashtable, it prints the value properly. But when I am iterating over it, it is unable to print anything.
Can someone point out what I am missing?
Just to mention, I have to use version 1.0 of PowerShell.
I have a text file to process. Text file has some configuration data and some networking commands. I want to run all those network commands and redirect output in some log file.
At starting of text file,there are some configuration information like File-name and file location. This can be used for naming log file and location of log file. These line starts with some special characters like '<#:'. just to know that rest of the line is config data about file not the command to execute.
Now, before i want start executing networking commands (starts with some special characters like '<:'), first i want to read all configuration information about file i.e. file name, location, overwrite flag etc. Then i can run all commands and dump output into log file.
I used get-content iterator to loop over entire text file.
Question: Is there any way to start looping over file from a specific line again?
So that i can process config information first (loop till i first encounter command to execute, remember this line number), create log file and then keep running commands and redirect output to log file (loop from last remembered line number).
Config File looks like:
<#Result_File_Name:dump1.txt
<#Result_File_Location:C:\powershell
<:ping www.google.com
<:ipconfig
<:traceroute www.google.com
<:netsh interface ip show config
My powerhsell script looks like:
$content = Get-Content C:\powershell\config.txt
foreach ($line in $content)
{
if($line.StartsWith("<#Result_File_Name:")) #every time i am doing this, even for command line
{
$result_file_arr = $line.split(":")
$result_file_name = $result_file_arr[1]
Write-Host $result_file_name
}
#if($line.StartsWith("<#Result_File_Location:"))#every time i am doing this, even for command line
#{
# $result_file_arr = $line.split(":")
# $result_file_name = $result_file_arr[1]
#}
if( $conf_read_over =1)
{
break;
}
if ($line.StartsWith("<:")) #In this if block, i need to run all commands
{
$items = $line.split("<:")
#$items[0]
#invoke-expression $items[2] > $result_file_name
invoke-expression $items[2] > $result_file_name
}
}
If all the config information starts with <# just process those out first separately. Once that is done you can assume the rest are commands?
# Collect config lines and process
$config = $content | Where-Object{$_.StartsWith('<#')} | ForEach-Object{
$_.Trim("<#") -replace "\\","\\" -replace "^(.*?):(.*)" , '$1 = $2'
} | ConvertFrom-StringData
# Process all the lines that are command lines.
$content | Where-Object{!$_.StartsWith('<#') -and ![string]::IsNullOrEmpty($_)} | ForEach-Object{
Invoke-Expression $_.trimstart("<:")
}
I went a little over board with the config section. What I did was convert it into a hashtable. Now you will have your config options, as they were in file, accessible as an object.
$config
Name Value
---- -----
Result_File_Name dump1.txt
Result_File_Location C:\powershell
Small reconfiguration of your code, with some parts missing, would look like the following. You will most likely need to tweak this to your own needs.
# Collect config lines and process
$config = ($content | Where-Object{$_.StartsWith('<#')} | ForEach-Object{
$_.Trim("<#") -replace "\\","\\" -replace "^(.*?):(.*)" , '$1 = $2'
} | Out-String) | ConvertFrom-StringData
# Process all the lines that are command lines.
$content | Where-Object{!$_.StartsWith('<#') -and ![string]::IsNullOrEmpty($_)} | ForEach-Object{
Invoke-Expression $_.trimstart("<:") | Add-Content -Path $config.Result_File_Name
}
As per your comment you are still curious about your restart loop logic which was part of your original question. I will add this as a separate answer to that. I would still prefer my other approach.
# Use a flag to determine if we have already restarted. Assume False
$restarted = $false
$restartIndexPoint = 4
$restartIndex = 2
for($contentIndex = 0; $contentIndex -lt $content.Length; $contentIndex++){
Write-Host ("Line#{0} : {1}" -f $contentIndex, $content[$contentIndex])
# Check to see if we are on the $restartIndexPoint for the first time
if(!$restarted -and $contentIndex -eq $restartIndexPoint){
# Set the flag so this does not get repeated.
$restarted = $true
# Reset the index to repeat some steps over again.
$contentIndex = $restartIndex
}
}
Remember that array indexing is 0 based when you are setting your numbers. Line 20 is element 19 in the string array for example.
Inside the loop we run a check. If it passes we change the current index to something earlier. The write-host will just print the lines so you can see the "restart" portion. We need a flag to be set so that we are not running a infinite loop.
I've started to use the start-transcript in my profile to keep a log of everything I do via the shell.
it's becoming useful for looking back at what changes are made and when they were made. I'm also beginning to use it as the first steps of documentation. I've been commenting the things done in the shell for future reference.
The thing that is proving tricky is the formatting is that of a text doc and is not as easy to read as the shell (error, verbose and warning colours mainly).
I was wondering if anybody uses the Transcript functionality in this way and has a viewer of preference or a script that parses the log file to produce a doc of some sort?
Edit: i'm interested to know why the question has been down voted...
I believe it will be very hard to parse a transcript to create an accurate formatted document. You could however use the console host API to capture (parts of) the screen buffer.
This Windows Powershell blog article describes how this works.
A trivial way to use the (modified) script (Get-ConsoleAsHtml.ps1) is to modify your prompt function, so that all lines from the buffer that haven't been written to your html transcript yet, are saved every time the prompt function is called. The first block of code is the contents of the modified script, the second block of code shows how you can use this script in your profile.
###########################################################################################################
# Get-ConsoleAsHtml.ps1
#
# The script captures console screen buffer up to the current cursor position and returns it in HTML format.
# (Jon Z: Added a startline parameter)
#
# Returns: UTF8-encoded string.
#
# Example:
#
# $htmlFileName = "$env:temp\ConsoleBuffer.html"
# .\Get-ConsoleAsHtml 5 | out-file $htmlFileName -encoding UTF8
# $null = [System.Diagnostics.Process]::Start("$htmlFileName")
#
param (
$startline = 0
)
# Check the host name and exit if the host is not the Windows PowerShell console host.
if ($host.Name -ne 'ConsoleHost')
{
write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
exit -1
}
# The Windows PowerShell console host redefines DarkYellow and DarkMagenta colors and uses them as defaults.
# The redefined colors do not correspond to the color names used in HTML, so they need to be mapped to digital color codes.
#
function Normalize-HtmlColor ($color)
{
if ($color -eq "DarkYellow") { $color = "#eeedf0" }
if ($color -eq "DarkMagenta") { $color = "#012456" }
return $color
}
# Create an HTML span from text using the named console colors.
#
function Make-HtmlSpan ($text, $forecolor = "DarkYellow", $backcolor = "DarkMagenta")
{
$forecolor = Normalize-HtmlColor $forecolor
$backcolor = Normalize-HtmlColor $backcolor
# You can also add font-weight:bold tag here if you want a bold font in output.
return "<span style='font-family:Courier New;color:$forecolor;background:$backcolor'>$text</span>"
}
# Generate an HTML span and append it to HTML string builder
#
function Append-HtmlSpan
{
$spanText = $spanBuilder.ToString()
$spanHtml = Make-HtmlSpan $spanText $currentForegroundColor $currentBackgroundColor
$null = $htmlBuilder.Append($spanHtml)
}
# Append line break to HTML builder
#
function Append-HtmlBreak
{
$null = $htmlBuilder.Append("<br>")
}
# Initialize the HTML string builder.
$htmlBuilder = new-object system.text.stringbuilder
$null = $htmlBuilder.Append("<pre style='MARGIN: 0in 10pt 0in;line-height:normal';font-size:10pt>")
# Grab the console screen buffer contents using the Host console API.
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
# Iterate through the lines in the console buffer.
for($i = $startline; $i -lt $bufferHeight; $i++)
{
$spanBuilder = new-object system.text.stringbuilder
# Track the colors to identify spans of text with the same formatting.
$currentForegroundColor = $buffer[$i, 0].Foregroundcolor
$currentBackgroundColor = $buffer[$i, 0].Backgroundcolor
for($j = 0; $j -lt $bufferWidth; $j++)
{
$cell = $buffer[$i,$j]
# If the colors change, generate an HTML span and append it to the HTML string builder.
if (($cell.ForegroundColor -ne $currentForegroundColor) -or ($cell.BackgroundColor -ne $currentBackgroundColor))
{
Append-HtmlSpan
# Reset the span builder and colors.
$spanBuilder = new-object system.text.stringbuilder
$currentForegroundColor = $cell.Foregroundcolor
$currentBackgroundColor = $cell.Backgroundcolor
}
# Substitute characters which have special meaning in HTML.
switch ($cell.Character)
{
'>' { $htmlChar = '>' }
'<' { $htmlChar = '<' }
'&' { $htmlChar = '&' }
default
{
$htmlChar = $cell.Character
}
}
$null = $spanBuilder.Append($htmlChar)
}
Append-HtmlSpan
Append-HtmlBreak
}
# Append HTML ending tag.
$null = $htmlBuilder.Append("</pre>")
return $htmlBuilder.ToString()
Example of a profile:
############################################################################################################
# Microsoft.PowerShell_profile.ps1
#
$docpath = [environment]::GetFolderPath([environment+SpecialFolder]::MyDocuments)
$transcript = "$($docpath)\PowerShell_transcript.$(get-date -f 'yyyyMMddHHmmss').html";
$global:lastloggedline = 0
function prompt {
&'D:\Scripts\Get-ConsoleAsHtml.ps1' $global:lastloggedline | out-file $transcript -append;
$global:lastloggedline = $host.ui.rawui.cursorposition.Y
"PS $pwd$('>' * ($nestedPromptLevel + 1)) "
}