How to write to a csv file with no newline using powershell? - powershell

I want each row to be populated with the data retrieved from each file. Currently, the 2nd and 3rd column entries are being written to a newline.CSV file output I have tried using "export-csv" and the "-nonewline" command. Perhaps there is a regex command that would solve this?
#Column headings
$headings = "Source file, Review file existence, Review Result, Compilation Check Result, Static Analysis Result, Review up-to-date, Reviewed code version, Latest code version"
# Create array with $headings as first input
$output = #($headings)
$SourceParentDir = "C:\Document"
$Files = get-childitem -Path $SourceParentDir -Recurse -Filter '*.?pp' | % { $_.FullName }
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
# Populate each row for each file
$output += $BaseName
$output += ", Review Exists" # writes to a newline
$output += ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","
}
# write output to a csv file
$output | Out-File -FilePath Documents\Example-csv.csv -encoding utf8

You can do things that way, but there's definitely a more-Powershelley way:
Get-ChildItem -Path $SourceParentDir -Recurse -Filter '*.?pp' |
ForEach-Object {
$File = $_
# Put your other code here
# This will output an object to the stream
[PSCustomObject]#{
'Source file' = $File.Name
'Review file existence' = 'Review Exists'
'Review Result' = $Result
'Compilation Check Result' = $Compilation
'Static Analysis Result' = $StaticAnalysis
'Review up-to-date' = $UpToDateFlag
'Reviewed code version' = $ReviewFileVersionNumber
'Latest code version' = $SourceFileVersionNumber
}
} | Export-Csv Example-csv.csv -NoTypeInformation
The big drawback here is that you don't get a lot of formatting choices about the CSV. Every field is quoted, for example.
Alternately, if you really want really detailed control of the $output string, you should use a StringBuilder instead of a String. StringBuilder is one of the most potent and widely used classes in C#. This is because strings in C# and Powershell are immutable, so when you += a String you create a new string, copy everything over with the new bit, then throw the old string away. It can be very memory intensive with large operations. StringBuilder lets you get around all that. It's a class that's designed to let you append stuff to strings and format them however you want.
You instance it like so:
$output = [System.Text.StringBuilder]::new()
And then you typically call one of two methods to add text. Append($string) appends the string, AppendLine($string) appends the line and then adds a newline. You can also call AppendLine() with no argument to just add a newline. To get your final string, you call the ToString() method. The append methods do return a status when you call them which you can prevent from outputting pretty easily with a [void], or by saving it to another variable if you need it.
$output = [System.Text.StringBuilder]::new()
[void]$output.AppendLine($headings)
$SourceParentDir = "C:\StarTeam\00011114-JSENS_TRS\ATR\04_SW_Implementation\Operational"
$Files = get-childitem -Path $SourceParentDir -Recurse -Filter '*.?pp' | % { $_.FullName }
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
# Populate each row for each file
[void]$output.Append($BaseName)
[void]$output.Append(", Review Exists")
[void]$output.Append(", $Result,$Compilation,$StaticAnalysis,$UpToDateFlag,$ReviewFileVersionNumber,$SourceFileVersionNumber,")
[void]$output.AppendLine()
}
$output.ToString() | Out-File -FilePath Documents\Example-csv.csv -encoding utf8

$output is an array, so each of those += inside the loop is a new entry in the array, and therefore a new line in the file.
You can fix this by using a temporary string variable in the middle of the loop, and appending it to $output once at the end of each iteration:
foreach ($File in $Files)
{
$row = [System.IO.Path]::GetFileName($File)
$row += ", Review Exists"
$row += ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","
$output += $row
}
or by putting everything in one big string interpolation:
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
$output += "$BaseName, Review Exists, $Result, $Compilation, $StaticAnalysis, $UpToDateFlag, $ReviewFileVersionNumber, $SourceFileVersionNumber"
}
But I agree with the other answer that building up an array of custom objects for the Export-Csv commandlet is more idiomatic for PowerShell.

The issue is how you're populating $Output. Since it is defined as an array, each time you're adding new information it's creating a new entry rather than just adding the additional information to a string.
If you make your $output into one line with all required fields it should correct it without you changing the array.
$output += $BaseName + ", Review Exists" + ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","

Related

Powershell replace from array strange behaviour

Problem is solved, but I don't understand why :-)
I have a Powershell script that perform replacements inside files (language metadata):
loads a list of replacement from a txt file into an array
gets all xml files from a Start folder
performs all the replacements from the array
performs a replacement on the filename based on the array first entry
saves the resulting files in a End folder
I've been using successfully variations of the exact same script for many years, with the only thing changing being the replacement file name and content... except today when creating another variant. The only change was the content of the substitution file, and suddenly the replacement did not happen anymore in the filename.
Here is the code:
#load the replacements from file
$data = Import-Csv -Path substitutions.txt -Header "Source", "Target", "Safe", "Count" -Delimiter "|"
#load the files to be processed
$xmlfiles = -join ($Startfolder, "*.xml")
$Fileset = Get-ChildItem $xmlfiles -recurse
foreach ($File in $Fileset) {
$NewFileName = ""
$WipFile = Get-Content $File
# set safe replacement flag to nothing
$flag = ""
#perform replacements
foreach ($item in $data) {
if ($WipFile -cmatch $item.Source) {
if ($item.Safe -eq 'yes') {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
}
else {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
$flag = "TOCHECK "
}
}
}
#replace language code in filename, based on first entry in the substitution list
$NewFileName = -join ($Endfolder, $flag, $file.name -creplace $data.Source[0], $data.Target[0])
Write-Host $NewFileName
#save file with updated content
$WipFile | Set-Content -Encoding Unicode ($File)
#move file to End folder
Move-Item $File $NewFileName
}
The substitution file is formatted as follows:
nl-NL|nl-BE|yes
After testing more, I discovered my new variant was failing if my substitution file had only one line. Add another one, and it works. How come?

List every sub folder in reverse

I have a UNC path
$path = "\\ad.testxyz.com\corp\technology\software\data\iso"
I need to apply permissions to the following paths:
\\ad.testxyz.com\corp\technology\software\data
\\ad.testxyz.com\corp\technology\software
\\ad.testxyz.com\corp\technology
And I don't want to do anything to \\ad.testxyz.com\corp\technology, or higher.
Here is my code so far. This "works", but I've run into a snag with directories that have spaces in them.
$path = "\\ad.domain.com\corp\technology\test\information\software"
#$path = "\\ad.domain.com\corp\technology\payer information\extracts\item load"
# Split string by "\", remove empty elements
$root = (($path.Split("\")).Split('',[System.StringSplitOptions]::RemoveEmptyEntries))
# Recursively list all top level folders in [path]. (Excludes DFS root)
for ($i=2; $i -lt $root.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $root[$i]
$branch += $leaf
$trunk = "\\" + $root[0] + "\" + $root[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\information
But if I try and use a path with spaces in it, I get the following
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer
\\ad.domain.com\corp\technology\payer\information
\\ad.domain.com\corp\technology\payer\information\extracts
\\ad.domain.com\corp\technology\payer\information\extracts\item
Figured it out. This works for anyone else.
$path = "\\ad.domain.com\corp\technology\test\payer information\software"
# Split string by "\", remove null elements
$tree = $path.Split([system.io.path]::DirectorySeparatorChar) | ? {$_}
# Recursively list all top level folders in [path]. (Excludes DFS root)
For ($i=2; $i -lt $tree.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $tree[$i]
$branch += $leaf
$trunk = "\\" + $tree[0] + "\" + $tree[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\payer information
there are builtin ways to handle path manipulation. [grin]
the simplest would be to use the .Parent property of the DirInfo object you get back from Get-ChildItem or from Get-Item when run against a filesystem. that likely cannot be used since it seems you only have strings to work with.
the next easiest is to use Split-Path and the -Parent switch to jump up one level at a time. that is what the below code does.
$SamplePathList = #(
'\\ad.domain.com\corp\technology\test\information\software'
'\\ad.domain.com\corp\technology\payer information\extracts\item load'
)
$StopAt = '\\ad.domain.com\corp\technology'
$Results = foreach ($SPL_Item in $SamplePathList)
{
$TempPath = $SPL_Item
# send starting path to $Results
$TempPath
while ($TempPath -ne $StopAt)
{
$TempPath = Split-Path -Path $TempPath -Parent
# send to $Results
$TempPath
}
}
$Results
output ...
\\ad.domain.com\corp\technology\test\information\software
\\ad.domain.com\corp\technology\test\information
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer information\extracts\item load
\\ad.domain.com\corp\technology\payer information\extracts
\\ad.domain.com\corp\technology\payer information
\\ad.domain.com\corp\technology

How to output to clipboard

I have a GUI listview with 3 columns (FullRowSelect enabled)
With a Copy button, that I want to copy the 3 columns of each row into the clipboard, so that there is a new line for each row.
But what happens is I get the 3 columns of each row joined together in one string.
NB the Write-Host's are just there to help me see what's going on, this is part of a no console GUI app in PowerShell
I've tried piping $output via Out-String | clip and others, but always get the same result of the whole lot being in one line.
I'm sure this is probably something very simple ! my PS skills are just basic !
Please can someone point me in the right direction :)
Function Copy2ClipBoard {
foreach ($line in $OutputlistView.SelectedItems) {
$allitems = ($line.SubItems[0].Text + " , " + $line.SubItems[1].Text + " , " + $line.SubItems[2].Text)
$Output += $allitems
Write-Host $allitems
}
Write-Host ""
Write-Host $Output
[System.Windows.Forms.Clipboard]::SetText($Output)
}
Will only work in PowerShell 5.x, since PowerShell 6 doesn't offer this cmdlet ...
You can use the ´Set-Clipboard` cmdlet.
Function Copy2ClipBoard {
# Clear the clipboard
Set-Clipboard
foreach ($line in $OutputlistView.SelectedItems) {
$allitems = ($line.SubItems[0].Text + " , " + $line.SubItems[1].Text + " , " + $line.SubItems[2].Text)
$Output += $allitems
Set-Clipboard -Value $allitems -Append
Write-Host $allitems
}
Write-Host ""
Write-Host $Output
}
You can check the clipboard via Get-Clipboard.
Hope that helps.
Your problem does not like with clip.
but always get the same result of the whole lot being in one line.
That is because you are just building a progressively longer string and not an array as you seem to think. When you do $Output += $allitems you are just adding the $allitems string to $output with a space in between. Moerwalds solution skirts around this by using the -Append parameter of Set-Clipboard.
In reality you could just deal with your input string in one of a few ways. Simplest would be piping into clip, you could also use something like -join "`r`n"
$OutputlistView.SelectedItems | Foreach-Object{
$_.SubItems[0].Text + " , " + $_.SubItems[1].Text + " , " + $_.SubItems[2].Text
} | clip

Re-run Batch File if 'Keyword' Exists

I want to create a script that re-runs a batch file if a specific keyword is found within a log. The problem that I am having is with the function used to check the file. Currently, when I run this script it exits saying that the expression "does not match" even though the keyword does, in fact, exist within the log file. In this case, the log file to check is named is 'output.log' and the keyword to match is called 'temporary'.
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_" + $current_date
"checking in directory... --> " + $file_path
$word_to_find = "temporary"
$file_to_check = "output.log"
"Searching for matching expression '" + $word_to_find + "' in file: " + $file_to_check
$containsWord = $file_to_check | %{$_ -match $word_to_find}
if ($containsWord -contains $true) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
} else {
'The expression does not match. Feed OK.'
}
Thats because you first have to get the content of the log, atm you are just comparing the string "output.log" against the string "temporary", which ofc returns the value 'false'.
If you want to keep your attempt, try it like this (Remember that output.log should either be in the running directory (than use .\ like here) or you have to give the full path to the log file):
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_"+ $current_date
"checking in directory... --> " + $file_path
$word_to_find="temporary"
$file_to_check=".\output.log"
"Searching for matching expression '" + $word_to_find + "' in file: " + $file_to_check
$containsWord = Get-Content $file_to_check | %{$_ -match $word_to_find}
If ($containsWord -contains $true) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
}
Else {
'The expression does not match. Feed OK.'
}
If you are up to some improvements, I would do it more like this:
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_"+ $current_date
Write-Host "checking in directory... --> $file_path"
$word_to_find="temporary"
$file_to_check="<fullPathToLog>\output.log"
Write-Host "Searching for matching expression '" + $word_to_find + "' in file: $file_to_check"
If((Get-Content $file_to_check) -match $word_to_find) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
}
Else {
'The expression does not match. Feed OK.'
}
This would save you the foreach and the extra variable.

how to access hashtable values in powershell if hashtable is generated in a function?

I have created a simple GUI to enter some values that are stored in a .txt file as soon as the User clicks OK.
For verification I'm displaying the data of the just created file with its input in a popup window.
As I want to use the data of the config file in several other .ps1 files that I'm using for a project, I started to move things into a globals.ps1 file. Everything works great, with the exception that I can no longer display the hashtable.
Here's what I have in my globals.ps1 :
# Wshell popup
$wshell = New-Object -ComObject Wscript.SHell
# read config
function readcfg
{
Get-Content -Path $cfg | foreach-object -begin { $conf = #{ } } -process {`
$key = [regex]::split($_, ':');
if (($key[0].CompareTo("") -ne 0) -and ($key[0].StartsWith("[") -ne $True))`
{ $conf.Add($key[0], $key[1]) }
}
}
And this is the part in my settings GUI which is executed if the OK button is pressed:
$cfgData = "
[Account]
Screds:" + $Admaccount.text + "
Spw:" + $PW + "
[Domainconfig]
Sdomain:" + $domain.text + "
SSearchBase:" + $searchBase.text + "
SdeactivatedUsers_OU:" + $deactivatedUsersOU.text + ""
Out-File -filepath $cfg -inputobject $cfgData -Force
Start-Sleep -s 1
$wshell.Popup("Settings saved:`nAccount: " + $conf.Screds + "`nDomain: " + $conf.Sdomain + "`nSearchBase: " + $conf.SSearchBase + "`ndeactivatedUsersOU " + $conf.SdeactivatedUsers_OU + "", 0, "Yarr...!", 0x0)
If I move the get-Content out of the readcfg function, I can display the values again. But that's of course not the solution, as it will display the old data if the settings are changed and the popup comes up again.
What am I missing here?
As stated by Kayasax in the comments, it was simply a "out of scope" issue.
after declaring the function and hashtable as global, it all works perfectly again.
# read config
function global:readcfg
{
Get-Content -Path $cfg | foreach-object -begin { $global:conf = #{ } } -process {`
$key = [regex]::split($_, ':');
if (($key[0].CompareTo("") -ne 0) -and ($key[0].StartsWith("[") -ne $True))`
{ $global:conf.Add($key[0], $key[1]) }
}
}