How to display PowerShell results horizontally instead of vertically? - windows

For example, you specified a variable as shown below.
$data1 = get-psdrive | where-object {$_.name -like 'c'} | select -expandproperty used
$data2 = get-psdrive | where-object {$_.name -like 'c'} | select -expandproperty free
echo $data1,data2
The output is vertical.
$data1
$data2
I used write-host -nonewline to display output horizontally, but the command does not export to txt
write-host $data1 -nonewline; write-host $data2 -nonewline >> c:\test.txt
How can I display horizontally and export in txt?

How about converting to json (or csv)? Note that ">>" can mix encodings, but add-content doesn't.
get-psdrive c | select used, free | ConvertTo-Json -Compress |
add-content test.txt
get-content test.txt
{"Used":217365741568,"Free":21004943360}
Or just join them. Too bad select -expand doesn't work with multiple properties.
psdrive c | % { ($_.used,$_.free) -join ',' }
217382371328,20988313600

You can't use Write-Host for this. Even if you change to Write-Host -NoNewLine it'll still never work because Write-Host is intended for directly writing into the screen and can't be redirected unless you use PowerShell 5+ and redirect the stream number 6 (Information stream)
Write-Host -NoNewLine $data1,$data2 6>output.txt
in which case it doesn't print out to string of course. In short Write-Host in PowerShell 5+ doesn't write to stdout but the Information stream
The real solution to writing to screen with the ability to redirect to file or pipe to another command is to use Write-Output (which echo is an alias to), and simply use a single string to write to a single line
Write-Output "$data1,$data2"
echo "$data1,$data2"
echo ($data1 + "," + $data2)
See Write-Host Considered Harmful for more details

Use a double quote string with your variables: Write-Host “$data1,$data2”
Read more https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.1

Related

Powershell replace file content with output of previous command [duplicate]

I am having a helluva time trying to understand why this script is not working as intended. It is a simple script in which I am attempting to import a CSV, select a few columns that I want, then export the CSV and copy over itself. (Basically we have archived data that I only need a few columns from for another project due to memory size constraints). This script is very simple, which apparently has an inverse relationship with how much frustration it causes when it doesn't work... Right now the end result is I end up with an empty csv instead of a csv containing only the columns I selected with Select-Object.
$RootPath = "D:\SomeFolder"
$csvFilePaths = Get-ChildItem $RootPath -Recurse -Include *.csv |
ForEach-Object{
Import-CSV $_ |
Select-Object Test_Name, Test_DataName, Device_Model, Device_FW, Data_Avg_ms, Data_StdDev |
Export-Csv $_.FullName -NoType -Force
}
Unless you read the input file into memory in full, up front, you cannot safely read from and write back to the same file in a given pipeline.
Specifically, a command such as Import-Csv file.csv | ... | Export-Csv file.csv will erase the content of file.csv.
The simplest solution is to enclose the command that reads the input file in (...), but note that:
The file's content (transformed into objects) must fit into memory as a whole.
There is a slight risk of data loss if the pipeline is interrupted before all (transformed) objects have been written back to the file.
Applied to your command:
$RootPath = "D:\SomeFolder"
Get-ChildItem $RootPath -Recurse -Include *.csv -OutVariable csvFiles |
ForEach-Object{
(Import-CSV $_.FullName) | # NOTE THE (...)
Select-Object Test_Name, Test_DataName, Device_Model, Device_FW,
Data_Avg_ms, Data_StdDev |
Export-Csv $_.FullName -NoType -Force
}
Note that I've used -OutVariable csvFiles in order to collect the CSV file-info objects in output variable $csvFiles. Your attempt to collect the file paths via $csvFilePaths = ... doesn't work, because it attempts to collects Export-Csv's output, but Export-Csv produces no output.
Also, to be safe, I've changed the Import-Csv argument from $_ to $_.FullName to ensure that Import-Csv finds the input file (because, regrettably, file-info object $_ is bound as a string, which sometimes expands to the mere file name).
A safer solution would be to output to a temporary file first, and (only) on successful completion replace the original file.
With either approach, the replacement file will have default file attributes and permissions; if the original file had special attributes and/or permissions that you want to preserve, you must recreate them explicitly.
As Matt commented, your last $PSItem ($_) not related to the Get-ChildItem anymore but for the Select-Object cmdlet which don't have a FullName Property
You can use differnt foreach approach:
$RootPath = "D:\SomeFolder"
$csvFilePaths = Get-ChildItem $RootPath -Recurse -Include *.csv
foreach ($csv in $csvFilePaths)
{
Import-CSV $csv.FullName |
Select-Object Test_Name,Test_DataName,Device_Model,Device_FW,Data_Avg_ms,Data_StdDev |
Export-Csv $csv.FullName -NoType -Force
}
Or keeping your code, add $CsvPath Variable containing the csv path and use it later on:
$RootPath = "D:\SomeFolder"
Get-ChildItem $RootPath -Recurse -Include *.csv | ForEach-Object{
$CsvPath = $_.FullName
Import-CSV $CsvPath |
Select-Object Test_Name,Test_DataName,Device_Model,Device_FW,Data_Avg_ms,Data_StdDev |
Export-Csv $CsvPath -NoType -Force
}
So I have figured it out. I was attempting to pipe through the Import-Csv cmdlet directly instead of declaring it as a variable in the o.g. code. Here is the code snippet that gets what I wanted to get done, done. I was trying to pipe in the Import-Csv cmdlet directly before, I simply had to declare a variable that uses the Import-Csv cmdlet as its definition and pipe that variable through to Select-Object then Export-Csv cmdlets. Thank you all for your assistance, I appreciate it!
$RootPath = "\someDirectory\"
$CsvFilePaths = #(Get-ChildItem $RootPath -Recurse -Include *.csv)
$ColumnsWanted = #('Test_Name','Test_DataName','Device_Model','Device_FW','Data_Avg_ms','Data_StdDev')
for($i=0;$i -lt $CsvFilePaths.Length; $i++){
$csvPath = $CsvFilePaths[$i]
Write-Host $csvPath
$importedCsv = Import-CSV $csvPath
$importedCsv | Select-Object $ColumnsWanted | Export-CSV $csvPath -NoTypeInformation
}

PowerShell Format-Table -AutoSize not Producing an Output File

When running the following line in PowerShell including the "Format-Table -AutoSize", an empty output file is generated:
Get-ChildItem -Recurse | select FullName,Length | Format-Table -AutoSize | Out-File filelist.txt
The reason I need the output file to be AutoSized is because longer filenames from the directoy are being trunacted. I am trying to pull all Filenames and File Sizes for all files within a folder and subfolders. When removing the -Autosize element, an output file is generated with truncated file names:
Get-ChildItem -Recurse | select FullName,Length | Out-File filelist.txt
Like AdminOfThings commented, use Export-CSV to get the untruncated values of your object.
Get-ChildItem -Recurse | select FullName,Length | Export-CSv -path $myPath -NoTypeInformation
I do not use Out-File much at all, and only use Format-Table/Format-List for interactive scripts. If I want to write data to a file, Select-Object Column1,Column2 | Sort-Object Column1| Export-CSV lets me select the properties of the object I am exporting that I want to export, and sort the records as needed. you can change the delimiter from a comma to tab/pipe/whatever else you may need.
While the other answer may address the issue, you may have other reasons for wanting to use Out-File. Out-File has a "Width" parameter. If this is not set, PowerShell defaults to 80 characters - hence your issue. This should do the trick:
Get-ChildItem -Recurse | select FullName,Length | Out-File filelist.txt -Width 250 (or any other value)
The Format-* commandlets in PowerShell are only intended to be used in the console. They do not actually produce output that can be piped to other commandlets.
The usual approach to get the data out is with Export-Csv. CSV files are easily imported into other scripts or spreadsheets.
If you really need to output a nicely formatted text file you can use .Net composite formatting with the -f (format) operator. This works similarly to printf() in C. Here is some sample code:
# Get the files for the report
$files = Get-ChildItem $baseDirectory -Recurse
# Path column width
$nameWidth = $files.FullName |
ForEach-Object { $_.Length } |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
# Size column width
$longestFileSize = $files |
ForEach-Object { $_.Length.tostring().Length } |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
# Have to consider that some directories will have no files with
# length strings longer than "Size (Bytes)"
$sizeWidth = [System.Math]::Max($longestFileSize, "Size (Bytes)".Length)
# Right-align paths, left-align file size
$formatString = "{0,-$nameWidth} {1,$sizeWidth}"
# Build the report and write it to a file
# ArrayList are much more efficient than using += with arrays
$lines = [System.Collections.ArrayList]::new($files.Length + 3)
# The [void] cast are just to prevent ArrayList.add() from cluttering the
# console with the returned indices
[void]$lines.Add($formatString -f ("Path", "Size (Bytes)"))
[void]$lines.Add($formatString -f ("----", "------------"))
foreach ($file in $files) {
[void]$lines.Add($formatString -f ($file.FullName, $file.Length.ToString()))
}
$lines | Out-File "Report.txt"

Retrieve parameters from file - Windows PowerShell

I am writing a super-easy script in PowerShell. The target of this script is to read a list of server names from a txt file and a command block from another txt file. The result of the operation shold be a third txt file containing the information.
Here some code:
cls
$usr = Read-Host "Please insert username, you'll be asked for password later"
$path = Read-Host "Insert a valid path for ServerList.txt file"
$serverList = Get-Content -Path $path | Out-String
$path = Read-Host "Insert a valid path fom Command.txt file"
$commandBlock = Get-Content -Path $path | Out-String
echo "Command: " $commandBlock "will be executed on " $serverList
echo "Press CTRL+Z to abort or"
pause
Invoke-Command -ComputerName $serverList -ScriptBlock { $commandBlock } -credential $usr
Serverlist.txt is a plain text containing something like "server1,server2,server3" and command.txt contain only this "Get-WmiObject Win32_BIOS | Select-Object SerialNumber"
Why the error is Invoke-Command : One or more computer names are not valid. If you are trying to pass a URI, use the -ConnectionUri parameter, or pass URI objects
instead of strings. ?
I even tried to substitute $serverlist with $serverlist.toString() but it's not working. I read somewhere that in this case $serverlist is an Array, how do I do to make everything work?
Consider that https://technet.microsoft.com/en-us/library/hh849719.aspx Invoke-Commands work with "server1,server2,server3" format if you put the string via console.
Your $serverList isn't a list, it's a single string of server1,server2 etc. To make it into an array, you can use -split to split the string by commas.
$serverList = Get-Content -Path $path | Out-String
$serverList = $serverList -split ","
For further understanding of why this doesn't work as you expect, please see the parsing and command syntax help files:
Get-Help about_Parsing
Get-Help about_Command_Syntax
$serverlist
When your text file contains the line server1,server2,server3, this command:
Get-Content -Path .\file.txt | Out-String
Just results in the string server1,server2,server3 and a newline - that's not a valid hostname.
Either format your text file like this (Get-Content automatically splits on line breaks):
server1
server2
server3
or split the string(s) from the file yourself:
$Serverlist = Get-Content -Path $Path | ForEach-Object { $_ -split "," }
$commandblock
For the command block part to work, you can't just drop a string into a ScriptBlock and expect it to execute - you need to recreate it as executable code:
$Code = Get-Content -Path $path -Raw
$CommandBlock = [scriptblock]::Create($Code)
# Now you can do this
Invoke-Command -ScriptBlock $CommandBlock

Using a text file as input to a Powershell script

My group is moving to a new network where we can't directly copy from our computer in Network A to the new machine in Network B. After years on this machine in Network A, I've got project files interspersed all over the disk. I need to build a script to copy the folders and files to a backup disk. No problem there, but the network tech guy requires the total byte count to be known before copying.
In CMD I've used dir /AD /S /B > C:\Users\r6540\Desktop\UserFiles.txt from C:\ to generate a huge list of directories, including a lot of junk that I've manually edited out.
e.g.
C:\Dev\SSIS
C:\Dev\SSIS\DatabaseCleanup
C:\Dev\SSIS\DatabaseMaintTests
C:\Dev\SSIS\EclipseKeys
C:\Dev\SSIS\TemplateProject
I've never used PowerShell, but it certainly looks like this task would be within its ability. I found this:
$startFolder = "C:\Scripts"
$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"
$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
{
$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
}
at Microsoft technet and also this, same page:
$objFSO = New-Object -com Scripting.FileSystemObject
"{0:N2}" -f (($objFSO.GetFolder("C:\Scripts").Size) / 1MB) + " MB"
The output I'm looking for is the directory name, a tab, and the folder size (without the "MB" as shown above though) and CRLF as the EOL written to a text file.
e.g.
C:\Dev\SSIS 70.23
C:\Dev\SSIS\DatabaseCleanup 17.80
C:\Dev\SSIS\DatabaseMaintTests 22.91
C:\Dev\SSIS\EclipseKeys 1.22
C:\Dev\SSIS\TemplateProject 13.29
Anyone know PowerShell well enough to troop through UserFiles.txt and get the resulting text file output?
Form doesn't matter as much as function--so if you can come up with an alternate approach, I'd be glad to see it.
Thanks.
This is pretty straightforward in PowerShell:
$objFSO = New-Object -com Scripting.FileSystemObject
Get-Content c:\input.txt |
Foreach { "{0}`t{1:N2}" -f $_, (($objFSO.GetFolder($_).Size) / 1MB) } |
Out-File c:\output.txt -enc ascii
This is assuming the FileSystemObject script you found works. :-)
Simple approach is to use the pipeline more efficiently
$inputFile = "C:\Users\r6540\Desktop\UserFiles.txt"
$outputFile = "C:\temp\output.txt"
Get-Content $inputFile | ForEach-Object{
$sum = Get-ChildItem $_ -recurse -Force | Measure-Object -property length -sum | Select-Object -ExpandProperty Sum
"{1}`t{0:N2}" -f ($sum / 1MB), $_
} | Set-Content $outputFile
So we take each line of the file and gather the size in MB using your posted logic. The send the output to file. But we can improve on that a little.
$inputFile = "C:\Users\r6540\Desktop\UserFiles.txt"
$outputFile = "C:\temp\output.txt"
$results = Get-Content $inputFile | ForEach-Object{
$props = #{
Folder = $_
Sum = "{0:N2}" -f ((Get-ChildItem $_ -Recurse -Force | Measure-Object -property length -sum | Select-Object -ExpandProperty Sum) / 1MB)
}
New-Object -TypeName PSCustomObject -Property $props
}
$results | Export-CSV $outputFile -NoTypeInformation
"Total,{0}" -f ($results | Measure-Object Sum -sum | Select-Object -ExpandProperty Sum) | Add-Content $outputFile
Only difference here is that we make nice CSV output as well as a total being appended to the bottom since at the end of the day that might be nice to know as well (If I was the recipient of multiple copies from multiple users I would save me a few seconds of effort.) You could just keep a counter in the loop as well for this but this give you an opportunity to see PowerShell at work.

RegEx with Powershell wrong?

So I have this PowerShell one-liner that gives me back my groups:
$groups = Get-ADPrincipalGroupMembership userName | ForEach-Object {Write-Host $_.Name -BackgroundColor Red} |Out-String; $groupsWithSpaces = foreach ($groups in $groupsWithSpaces) {Select-String -AllMatches '[\\s+,]' | Write-Host}; Write-Host $groupsWithSpaces
How am I using the regex wrong? I am trying to return only groups that have a whitespace on the end. I am always returning every group, and I only want the ones that have white space on the end. I can get true/false, but...help please.
Thanks!
In your regex you were escaping one of the slashes. Making it so you were looking for groups with a literal slash followed by at least on letter s.
If you are just trying to find the group with spaces in them this would work
Get-ADPrincipalGroupMembership username |
Where-Object{$_.Name -match "\s"} |
select -ExpandProperty Name |
Write-Host -BackgroundColor red
I formatted it for easy reading
If you wanted to see the whole list with the empty ones standing out you could also use and if statement
Get-ADPrincipalGroupMembership username | select -ExpandProperty Name | ForEach-Object{
If( $_ -match "\s"){
Write-Host $_ -BackgroundColor red
} Else {
Write-Host $_
}
}
I think this is the false variable --> $groupsWithSpaces = foreach ($groups in -->HERE<--$groupsWithSpaces-->HERE<--) {Select-String -AllMatches '[\\s+,]' | Write-Host}; Write-Host $groupsWithSpaces.
Work this one?
$groups = Get-ADPrincipalGroupMembership userName | ForEach-Object {Write-Host $_.Name -BackgroundColor Red} |Out-String; $groupsWithSpaces = foreach ($group in $groups) {If ($group[-1] -eq " ") {Write-Host $group}}; Write-Host $groupsWithSpaces

Resources