This question already has an answer here:
Convert hashtable back to string data in efficient way
(1 answer)
Closed 2 years ago.
I have found following code for reading hashtable from a text file with key=value pairs.
$value = Get-Content $Path | Out-String | ConvertFrom-StringData
I have not found the inverse operation though how to write the hashtable back to the text file in the same format. I use the following code
$values.GetEnumerator() | ForEach-Object { "{0}={1}" -f $_.Name,$_.Value } | Set-Content $Path
It works fine. I just wonder if there is some more straight forward solution.
Not that I know. This has been asked before.
Your solution looks fine. You could turn your code into a script cmdlet:
function ConvertTo-StringData {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[HashTable[]]$HashTable
)
process {
foreach ($item in $HashTable) {
foreach ($entry in $item.GetEnumerator()) {
"{0}={1}" -f $entry.Key, $entry.Value
}
}
}
}
Example:
$hashtable = Get-Content $path | ConvertFrom-StringData
$hashtable | ConvertTo-StringData | Set-Content $path
# or
ConvertTo-StringData $hashtable | Set-Content $path
Related
This question already has answers here:
Expanding variables in file contents
(3 answers)
Closed 3 years ago.
I have a number of .txt files that I use as standardized templates for Arista switch deployments, but I want to update them rapidly using PowerShell.
I replaced all the necessary values in these templates with $variables and I now am attempting to write a script to replace them.
I found a solution that I liked, but it is not working for me. I am not sure what I am doing wrong here.
https://stackoverflow.com/a/9326779/
This is a snippet from the source file :
router bgp $asn
router-id 10.1.1.1
bgp listen range 192.168.$id.0/25 peer-group cluster$id remote-as $asn
neighbor cluster$id peer-group
neighbor cluster$id update-source Loopback0
neighbor cluster$id description cluster$id-BGP
neighbor cluster$id ebgp-multihop 3
neighbor cluster$id maximum-routes 12000
network 10.1.1.1/32
exit
Here is a snippet from the powershell script :
$newvars = #{
'$id' = '101'
'$asn' = '12345'
}
$template = '.\Arista\arista.txt'
$destination_file = '.\switchconfig' + $id + '.txt'
Get-Content -Path $template | ForEach-Object {
$line = $_
$newvars.GetEnumerator() | ForEach-Object {
if ($line -match $_.Key)
{
$line = $line -replace $_.Key, $_.Value
}
}
$line
} | Set-Content -Path $destination_file
What I want is to have a group of variables defined (upwards of 30), and then replace each instance of that variable in the text file with the value contained in the script.
This solution seemed good, since it would avoid doing a "replace" over and over, but it just prints the file as it originally was.
Since powershell uses $ as an identifier for variables (reserved), you have to properly escape that when running your method. Following is a little off but does what you are looking for. update your dictionary with \ before your $ sign to replace text including $.
$newvars = #{
'\$id' = '101'
'\$asn' = '12345'
}
$template = "C:\temp\new.txt"
$destination_file = "C:\temp\replaced.txt"
$data = #()
foreach($line in Get-Content $template) {
foreach($key in $newvars.Keys) {
if ($line -match $key) {
$line = $line -replace $key, $newvars[$key]
}
}
$data += $line
}
$data | Out-File $destination_file
Another thing to note.. in your file you are defining $id as the name of the file. I am not sure where but that variable would always be null as its not defined yet (unless your snippet here is different from your actual code.
If you want to use Invoke-Expression, you can use it in the following way,
$id = '101'
$asn = '12345'
$template = (Get-Content "C:\temp\new.txt") | out-string
$data = Invoke-Expression "`"$template`""
$data | Out-File "C:\Temp\test.txt"
You'll have to make sure your variables ($id, $asn) have a value to replace when evaluating the variables within your text file.
This question already has answers here:
Runtime of Foreach-Object vs Foreach loop
(3 answers)
Closed 6 years ago.
I would like to ask you pretty easy question about foreach and output,
I noticed that while we are trying to run foreach and then export it by out-file it is more complicated, my question is what is the right way to export the content with foreach ?
For instance I prepare this:
Get-Content C:\Windows.old\1.txt
$output =
Foreach ($line in $file)
{
$line + " "+ $line.Length + " is the lengh of you user name"
}
And I know that is incorrect, I am looking for good explanation
Why is it more complicated? I would use the Foreach-Object cmdlet (but you can also go with the loop) and pipe the result to the Set-Content cmdlet (but you can also go with Out-File:
$filePath = 'C:\Windows.old\1.txt'
$content = Get-Content $filePath
$content | ForEach-Object {
"$_ $($_.Length) is the lengh of you user name"
} | Set-Content $filePath
Note: You may need to set the encoding.
Edit: Without changing the script:
Get-Content C:\Windows.old\1.txt
$output = Foreach ($line in $file)
{
$line + " "+ $line.Length + " is the lengh of you user name"
}
$output| Out-File 'your-new-filepath.txt'
I have several files that I need to add a "!" to the beginning, just on the first line. I still need to keep the first line's content, just add a "!" as the first character.
Any help would be really appreciated.
Thanks!
Edit:
The only thing I could figure out so far was to do the following:
$a = Get-Content 'hh_Regulars3.csv'
$b = '!'
Set-Content 'hh_Regulars3-new.csv' -value $b,$a
This just added the "!" to the top of the file, instead of to the beginning of the first line.
You sent an array to Set-Content with $b,$a. Each array item will be given its own line as you have seen. It would displayed the same way on the prompt if executed.
As long as the file is not too big read it in as one string and add the character in.
$path = 'hh_Regulars3.csv'
"!" + (Get-Content $path -Raw) | Set-Content $path
If you only have PowerShell 2.0 then Out-String would work in place of -Raw
"!" + (Get-Content $path | Out-String) | Set-Content $path
The brackets are important to be sure the file is read in before it goes to through the pipeline. It allows us to both read and write on the same pipeline.
If the file is larger look into using StreamReaders and StreamWriters. This would also have to be used if the trailing new line, created by the Add-Content and Set-Content, is not warranted.
Late to the party, but thought this might be useful. I needed to perform the operation over a thousand+ large files, and needed something a little more robust and less prone to OOM exceptions. Ended up just writing it leveraging .Net libraries:
function PrependTo-File{
[cmdletbinding()]
param(
[Parameter(
Position=1,
ValueFromPipeline=$true,
Mandatory=$true,
ValueFromPipelineByPropertyName=$true
)]
[System.IO.FileInfo]
$file,
[string]
[Parameter(
Position=0,
ValueFromPipeline=$false,
Mandatory=$true
)]
$content
)
process{
if(!$file.exists){
write-error "$file does not exist";
return;
}
$filepath = $file.fullname;
$tmptoken = (get-location).path + "\_tmpfile" + $file.name;
write-verbose "$tmptoken created to as buffer";
$tfs = [System.io.file]::create($tmptoken);
$fs = [System.IO.File]::Open($file.fullname,[System.IO.FileMode]::Open,[System.IO.FileAccess]::ReadWrite);
try{
$msg = $content.tochararray();
$tfs.write($msg,0,$msg.length);
$fs.position = 0;
$fs.copyTo($tfs);
}
catch{
write-verbose $_.Exception.Message;
}
finally{
$tfs.close();
# close calls dispose and gc.supressfinalize internally
$fs.close();
if($error.count -eq 0){
write-verbose ("updating $filepath");
[System.io.File]::Delete($filepath);
[System.io.file]::Move($tmptoken,$filepath);
}
else{
$error.clear();
write-verbose ("an error occured, rolling back. $filepath not effected");
[System.io.file]::Delete($tmptoken);
}
}
}
}
Usage:
PS> get-item fileName.ext | PrependTo-File "contentToAdd`r`n"
This oneliner might works :
get-ChildItem *.txt | % { [System.Collections.ArrayList]$lines=Get-Content $_;
$lines[0]=$lines[0].Insert(0,"!") ;
Set-Content "new_$($_.name)" -Value $lines}
Try this:
$a = get-content "c:\yourfile.csv"
$a | %{ $b = "!" + $a ; $b | add-content "c:\newfile.csv" }
I want to changing some values in an INI file. Unfortunately, I have keys in 2 different sections which share an identical name but need different values. My code uses the Get-IniContent function from PsIni.
Example INI file:
[PosScreen]
BitmapFile=C:\Temp\Random.bmp
Bitmap=1
[ControlScreen]
BitmapFile=C:\Temp\Random.bmp
Bitmap=1
I need to change the above to the following:
[PosScreen]
BitmapFile=C:\Temp\FileC.bmp
Bitmap=1
[ControlScreen]
BitmapFile=C:\Temp\FileD.bmp
Bitmap=1
The PowerShell code I am using seems to work, but it changes every value to "File D". It is obviously parsing everything twice, and the name is the same for each section.
$NewFileC = "C:\Temp\FileC.bmp"
$NewFileD = "C:\Temp\FileD.bmp"
$POSIniContent = Get-IniContent "C:\scripts\Update-EnablerImage\WINSUITE.INI"
$BOIniContent = Get-IniContent "C:\scripts\Update-EnablerImage\WINSUITE.INI"
If ($POSIniContent["PosScreen"]["BitmapFile"] -ne $NewFileC) {
Get-Content "C:\scripts\Update-EnablerImage\WINSUITE.INI" |
ForEach-Object {$_ -replace "BitmapFile=.+" , "BitmapFile=$NewFileC" } |
Set-Content "C:\scripts\Update-EnablerImage\WINSUITE.INI"
}
If ($BOIniContent["ControlScreen"]["BitmapFile"] -ne $NewFileD) {
Get-Content "C:\scripts\Update-EnablerImage\WINSUITE.INI" |
ForEach-Object {$_ -replace "BitmapFile=.+" , "BitmapFile=$NewFileD" } |
Set-Content "C:\scripts\Update-EnablerImage\WINSUITE.INI"
}
My struggle is how to change each one independently. I'm a bit of a scripting newbie, so calling out for some help. Tried using Conditional Logic (ForEach $line in $INIFile, for example), but no luck with that.
You are overcomplicating things. You can use Get-IniContent and Out-IniFile as follows:
$ini = Get-IniContent c:\temp\ini.ini
$ini["posscreen"]["BitmapFile"] = "C:\Temp\FileC.bmp"
$ini | Out-IniFile -FilePath c:\temp\ini2.ini
Note that if you want to overwrite the original file, you must add -Force to the Out-IniFile call.
There's a GNU program called sponge that soaks up input before writing to a file so you can do something like this: cat myFile | grep "myFilter" | sponge myFile
Is there a powershell equivalent, so I can work on a file in place, without having to pipe to a temporary file?
Thanks
In Powershell, judicious use of parentheses will force an operation to completely finish before passing data to the next command in the pipeline. The default for piping Get-Content is to pipe line by line to the next command, but with parentheses it must form a complete data set (e.g., load all lines) before continuing:
(Get-Content myFile) | Select-String 'MyFilter' | Set-Content myFile
An alternative that may use less memory (I have not benchmarked it) is to only force the results of Select-String to complete before continuing:
(Get-Content myFile | Select-String 'MyFilter') | Set-Content myFile
You could also assign things to a variable as an additional step. Any technique will load the contents into the Powershell session's memory, so be careful with big files.
Addendum: Select-String returns MatchInfo objects. Using Out-File adds pesky extra blank lines due to the way it tries to format the results as a string, but Set-Content correctly converts each object to its own string as it writes, producing better output. Being that you're coming from *nix and are used to everything returning strings (whereas Powershell returns objects), one way to force string output is to pipe them through a foreach that converts them:
(Get-Content myFile | Select-String 'MyFilter' | foreach { $_.tostring() }) | Set-Content myFile
You can try this :
(Get-content myfile) | where {$_ -match "regular-expression"} | Set-content myfile
or
${full-path-file-name-of-myfile} | where {$_ -match "regular-expression"} | add-content Anotherfile
more easier to keep in mind
two other ways come to mind - they are both the same really just one is a function the other is on the command line. (I don't know sponge on unix so I can't say for certain they mimic it).
here's the first on the command line
Get-Content .\temp.txt |
Select-String "grep" |
foreach-object -begin { [array] $out = #()} -process { $out = $out + ($_.tostring())} -end {write-output $out}
and the second is two create a function to do it
function sponge {
[cmdletbinding()]
Param(
[Parameter(
Mandatory = $True,
ValueFromPipeline = $True)]
[string]$Output
)
Begin {
[array] $out = #()
}
Process {
$out = $out + $Output
}
End {
Write-Output $Out
}
}
Get-Content .\temp2.txt | Select-String "grep" | sponge
HTH,
Matt