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
Related
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 + ","
In a loop, the code will output text to a file, basically two vars, however the width of each var is variable and the output looks ugly.
Is there a way to output the two vars in "columns", as a table?
Current code in the loop (the variables are concatenated in the sample code):
$matches[0] + $cal | Add-Content -LiteralPath $cleancalendarslist
i agree csv is a plain text solution, and it doesn't require opening with excel - but i think this is what you are looking for: and it is using PadLeft()
$matches = #('short','this is a long string','a med string')
$maxlength = ($matches | Measure-Object -Maximum -Property Length).Maximum
$cal = '20190107' #not sure what cal is....
foreach($s in $array){
$paddedstring = $s.Padleft($maxlength,' ')
$paddedstring + ' ' + $cal | add-content -LiteralPath $cleancalendarslist
}
I'm in trouble here. newPath is a parameter of the function analyse which is written here. I have problems with my conditions...
The objective is to write every address in the .txt if it isn't there yet and if mSize (result from a working function) is greater than seuil (threshold). "seuil" equals 0 here. I'm matching the code in a folder, and every folder after this one is analysed.
Problem is, it works fine... For 2 addresses only.
I dont get it, when I launch the code, only two lines are written. With tests, I think "result" actually takes the null value and I don't know when and why.
$result = get-content -Path O:\folder\public\F*******\parcoursArborescence\logs.txt
$test = "start"
$test
cd $newPath
$newPath
$result
$dirSize = [math]::round($(getDirSize)/1048576, 2)
$mSize = [math]::round($(multimediaSize), 2)
if (($result -match [regex]::Escape($newPath + ";") -eq $FALSE)) {
$testR = $false
}
if (($result -match [regex]::Escape($newPath + ";") -eq $TRUE)) {
$testR = $true
}
$mSize
$testR
if ($mSize -gt $seuil -and $testR -eq $FALSE) {
$dataWriting = "writing..."
$dataWriting
$data = $newPath + ";" + $dirSize + ";" + $mSize + ";" + $(mmPercent) + ";" + $(getLastWriting) + ";" + $(getLastAccess)
$data | out-file -append O:\folder\public\F*******\parcoursArborescence\logs.txt -Encoding ASCII
}
dir |
foreach-object {
if ($_.PsISContainer -eq $True -and $result -notMatch [regex]::escape($_.fullname + ";")) {
$oldPath = $newPath
$newPath = $_.fullname
analyse $newPath
}
else {
$oldPath = cd ..\ | get-location
$newPath = $oldPath
}
}
Here are some lines of my textfile :
O:\folder\public\DataIntegrator;1030.95;812.7;79;08/13/2013 13:28:49;11/25/2015 09:47:28
O:\folder\public\DataIntegrator\package;988.99;810.93;82;08/13/2013 13:28:49;11/25/2015 09:47:28
My work is to classify differents types of files. I want the textfile to be full of adresses which guide to heavy folders (where length < "seuil"). The condition i'm working on is supposed not to allow adresses already in the .txt to be written again. These two lines are the only ones working atm ; after this, no address is written. I tried to put some tests values here and there ; the important one (I guess) is the "testR" one. At the beginning, it works and says its value is either true or false. But, I dont know why and when, it begins to say absolutely nothing, letting me guess it gets a null value.
Exemple of newPath : 0:\folder\public\DataIntegrator
-> Analyses DataIntegrator
EDIT : I modified the code a little. Here it is :
http://pastebin.com/zKaK2ZWX
For the problem, the only lines that matter are the 42 first. Even with all these conditions, testR doesnt change and stays false. It never confirm one of the 2 other conditions. If I switch testR to true, then it will stay true.
Okay, I think I got it.
I did more and more tests about the -match condition. It looks like the output isnt a boolean, but a String ; it sends me the lines of $result which contain the String $testPath, so the result never is either false or true.
But I have a new problem, then. How can I get a boolean out of it ? I need to know if $result contains $testPath.
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]) }
}
}
My first excursion into a real app in Powershell; I could write the code in a traditional .Net language, but I think Powershell is ideal for console apps like this. I've searched StackOverflow and found the (nearly) exact script I need; my issue is that I want to take a folder of files (on server fsA) and create 7-Zip archives on fsB named by the file date. For instance, all the files that were last written on 7/21/12 would be found in archive 20120721.7z. I'm basing this on the solution found here: Create a 7z Archive for files created by date using Powershell
Here is what I have so far:
$prog_dir = "C:\Progra~1\7-Zip\7z.exe"
$archive_dir = "\\fsa\Backup"
$input_dir = "\\fsb\Xml\Archive\"
$7zOpts = "-m0=PPMd:o32:mem192M"
$groups = dir $input_dir | group-object -property {$_.LastWriteTime.Date}
$groups | foreach{$cmd = $prog_dir + " a " + $archive_dir + "\$((Get-Date $_.Name).ToString(`"yyyyMMdd`")).7z $([string]::join(`" `", $_.Group)) " + $7zOpts} #; invoke-expression $cmd}
#
#Where to put this: "+ $input_dir +"???
#
$cmd
I can't seem to find a way to specify the input directory in the foreach line; I can put it at the beginning of the join statement, but it only adds the input directory to the first file in the list. For instance:
$groups | foreach{$cmd = $prog_dir + " a " + $archive_dir + "\$((Get-Date $_.Name).ToString(`"yyyyMMdd`")).7z " + $input_dir + "$([string]::join(`" `", $_.Group)) " + $7zOpts} #; invoke-expression $cmd}
produces the output
C:\Progra~1\7-Zip\7z.exe a \\fsa\Backup\20120722.7z \\fsb\Xml\Archive\255IQR.xml 2573WV.xml 257RMC.xml
where the last two files do not have the full path prepended? What am I doing wrong?
PowerShell is a shell, so running commands is pretty much the thing it should do well. So generally, if you resort to Invoke-Expression when running commands you're doing something very wrong. Let's try a different way:
$prog_dir = 'C:\Progra~1\7-Zip\7z.exe' # you don't need 8.3 names here, by the way
$archive_dir = '\\fsa\Backup'
$input_dir = '\\fsb\Xml\Archive\'
$7zOpts = '-m0=PPMd:o32:mem192M'
$groups = dir $input_dir | group-object -property {$_.LastWriteTime.Date}
$groups | foreach {
$files = $_.Group | select -expand FullName
$date = (Get-Date $_.Name).ToString('yyyyMMdd')
& $prog_dir a $archive_dir\$date.7z $files $7zOpts
}