Pipe output to the clipboard using PowerShell - powershell

EDIT: 23 Oct 2020
See postanote's answer.
EDIT: 14 May 2015
After 3 years, I thought I would share my ClipboardModule (I hope I am allowed to):
Add-Type -AssemblyName System.Windows.Forms
Function Get-Clipboard {
param([switch]$SplitLines)
$text = [Windows.Forms.Clipboard]::GetText();
if ($SplitLines) {
$xs = $text -split [Environment]::NewLine
if ($xs.Length -gt 1 -and -not($xs[-1])) {
$xs[0..($xs.Length - 2)]
} else {
$xs
}
} else {
$text
}
}
function Set-Clipboard {
$in = #($input)
$out =
if ($in.Length -eq 1 -and $in[0] -is [string]) { $in[0] }
else { $in | Out-String }
if ($out) {
[Windows.Forms.Clipboard]::SetText($out);
} else {
# input is nothing, therefore clear the clipboard
[Windows.Forms.Clipboard]::Clear();
}
}
function GetSet-Clipboard {
param([switch]$SplitLines, [Parameter(ValueFromPipeLine=$true)]$ObjectSet)
if ($input) {
$ObjectSet = $input;
}
if ($ObjectSet) {
$ObjectSet | Set-Clipboard
} else {
Get-Clipboard -SplitLines:$SplitLines
}
}
Set-Alias cb GetSet-Clipboard
Export-ModuleMember -Function *-* -Alias *
I usually use the cb alias (for GetSet-Clipboard) because it is two way i.e can get or set the clipboard:
cb # gets the contents of the clipboard
"john" | cb # sets the clipboard to "john"
cb -s # gets the clipboard and splits it into lines

If you have WMF 5.0, PowerShell contains two new cmdlets:
get-clipboard and set-clipboard

EDIT: Please look at question instead for solution.
Here is my solution:
Add-Type -AssemblyName 'System.Windows.Forms'
filter Set-Clipboard {
begin {
$cp = #()
}
process {
$_ | Tee-Object -Variable 'cp0'
$cp = $cp + #($cp0);
}
end {
$str = ($cp | Out-String).ToString();
[Windows.Forms.Clipboard]::Clear();
if ( ($str -ne $null) -and ($str -ne '') ) {
[Windows.Forms.Clipboard]::SetText( $str )
}
$cp = #()
}
}
This collects all the objects in an array, $cp. We use Tee-Object to redirect the current element, $_, to both the next process and to store it in the array, $cp. Lastly, once the process is finished we set the clipboard's text.
I have used it in the following way:
dir -Recurse | Set-Clipboard | Select 'Name'
And it seems to work.
To use a function instead:
function Set-Clipboard-Func {
$str = $input | Out-String
[Windows.Forms.Clipboard]::Clear();
if ( ($str -ne $null) -and ($str -ne '') ) {
[Windows.Forms.Clipboard]::SetText( $str )
}
}

Powershell version 6.1 removed this commandlet, so it is no longer built-in.
Instead, you need to install the ClipboardText package. In Powershell's console type:
Install-Module -Name ClipboardText
Then you can use:
Set-ClipboardText "hello clipboard"
Get-ClipboardText
Here is the github issue with the maintainers of Powershell redirecting you to use the ClipboardText package.

Native clip cmdlets in PSv7
$Host
# Results
<#
Name : ConsoleHost
Version : 7.0.3
InstanceId : 54be9bfd-799d-4213-a13a-22403c1d9ed8
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
#>
Get-Command -Name '*clip*'|Format-Table -a
# Results
<#
CommandType Name Version Source
----------- ---- ------- ------
Function Get-Clipboard 1.3.6 PowerShellCookbook
Function Set-Clipboard 1.3.6 PowerShellCookbook
Function Start-ClipboardHistoryViewer 0.0 ModuleLibrary
Cmdlet Get-Clipboard 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Set-Clipboard 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Set-UDClipboard 2.9.0 UniversalDashboard
Application clip.exe 10.0.19041.1 C:\WINDOWS\system32\clip.exe
Application ClipRenew.exe 10.0.19041.1 C:\WINDOWS\system32\ClipRenew.exe
Application ClipUp.exe 10.0.19041.488 C:\WINDOWS\system32\ClipUp.exe
Application rdpclip.exe 10.0.19041.423 C:\WINDOWS\system32\rdpclip.exe
#>

get-clipboard
skips newline characters when text is entered sequentially.
I use
[System.Windows.Forms.Clipboard]::GetText()
as before.

Now that Get-clipboard and Set-Clipboard are built in PSv7
You can have this function in your profile:
"C:\Users<USER_ID>\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1"
function To-Notepad {
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[object]
$InputObject
)
begin { $objs = #() }
process { $objs += $InputObject }
end {
$old = Get-clipboard # store current value
$objs | out-string -width 1000 | Set-Clipboard
& "notepad2" /c
sleep -mil 500
$old | Set-Clipboard # restore the original value
}
}
And then use in this way:
dir -Path C:\Temp | To-Notepad

Related

Comparing two text files and output the differences in Powershell

So I'm new to the Powershell scripting world and I'm trying to compare a list of IPs in text file against a database of IP list. If an IP from (file) does not exist in the (database) file put it in a new file, let's call it compared.txt. When I tried to run the script, I didn't get any result. What am I missing here?
$file = Get-Content "C:\Users\zack\Desktop\file.txt"
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
foreach($line1 in $file){
$check = 0
foreach($line2 in $database)
{
if($line1 != $line2)
{
$check = 1
}
else
{
$check = 0
break
}
}
if ($check == 1 )
{
$line2 | Out-File "C:\Users\zack\Desktop\compared.txt"
}
}
There is a problem with your use of PowerShell comparison operators unlike in C#, equality and inequality are -eq and -ne, and since PowerShell is a case insensitive language, there is also -ceq and -cne.
There is also a problem with your code's logic, a simple working version of it would be:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
# iterate each line in `file.txt`
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
# iterate each line in `database.txt`
# this happens on each iteration of the outer loop
$check = foreach($line2 in $database) {
# if this line of `file.txt` is the same as this line of `database.txt`
if($line1 -eq $line2) {
# we don't need to keep checking, output this boolean
$true
# and break the inner loop
break
}
}
# if above condition was NOT true
if(-not $check) {
# output this line, can be `$line1` or `$line2` (same thing here)
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
However, there are even more simplified ways you could achieve the same results:
Using containment operators:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
if($line1 -notin $database) {
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
Using Where-Object:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
Get-Content "C:\Users\zack\Desktop\file.txt" | Where-Object { $_ -notin $database } |
Set-Content path\to\comparisonresult.txt
Using a HashSet<T> and it's ExceptWith method (Note, this will also get rid of duplicates in your file.txt):
$file = [System.Collections.Generic.HashSet[string]]#(
Get-Content "C:\Users\zack\Desktop\file.txt"
)
$database = [string[]]#(Get-Content "C:\Users\zack\Desktop\database.txt")
$file.ExceptWith($database)
$file | Set-Content path\to\comparisonresult.txt

Writing an output on a .txt file on Powershell

I found a little script to get all the local groups and members and it's working perfectly but I need to write the output on PowerShell.
Trap {"Error: $_"; Break;}
function EnumLocalGroup($LocalGroup) {
$Group = [ADSI]"WinNT://$strComputer/$LocalGroup,group"
"`r`n" + "Group: $LocalGroup"
$Members = #($Group.psbase.Invoke("Members"))
foreach ($Member In $Members) {
$Name = $Member.GetType().InvokeMember("Name", 'GetProperty', $Null, $Member, $Null)
$Name
}
}
$strComputer = gc env:computername
"Computer: $strComputer"
$computer = [adsi]"WinNT://$strComputer"
$objCount = ($computer.PSBase.Children | Measure-Object).Count
$i = 0
foreach ($adsiObj in $computer.PSBase.Children) {
switch -regex ($adsiObj.PSBase.SchemaClassName) {
"group" {
$group = $adsiObj.Name
EnumLocalGroup $group
}
}
$i++
}
I already tried this:
function EnumLocalGroup($LocalGroup) | Out-File -FilePath "E:\PS\Malik\group.txt"
But the code won't start if I do that. I also tried to use this whole Out-File line at the end of the code after the } but doesn't work either and this is the only solution I find on Internet.
If you want to incorporate logging into a function you need to put it into the function body, e.g.
function EnumLocalGroup($LocalGroup) {
....
$foo = 'something'
$foo # output returned by function
$foo | Add-Content 'log.txt' # output to log file
...
}
or
function EnumLocalGroup($LocalGroup) {
...
$foo = 'something'
$foo | Tee-Object 'log.txt' -Append # output goes to log file and StdOut
...
}
Otherwise you have to do the logging when you call the function:
EnumLocalGroup $group | Add-Content 'C:\log.txt'

ConvertTo-CSV -UseCulture ignores current thread's culture

Question
Is it possible to force PowerShell to export to CSV in French format when run in a Windows Session with en-GB culture?
More Info
I'm hoping to export some data to CSV using the French culture rules (i.e. CSV's delimiter set to semicolon, but also with numbers using commas for decimal places, and other cultural formatting differences; so just using the -Delimiter parameter is not sufficient).
I came up with the below code (based on https://stackoverflow.com/a/7052955/361842)
function Set-Culture
{
[CmdletBinding(DefaultParameterSetName='ByCode')]
param (
[Parameter(Mandatory,ParameterSetName='ByCode',Position=1)]
[string] $CultureCode
,
[Parameter(Mandatory,ParameterSetName='ByCulture',Position=1)]
[System.Globalization.CultureInfo] $Culture
)
begin {
[System.Globalization.CultureInfo] $Culture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
}
process {
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $Culture
[System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture
}
}
function Invoke-CommandInCulture {
[CmdletBinding()]
param (
[Parameter(Mandatory,ParameterSetName='ByCode',Position=1)]
[string]$CultureCode
,
[Parameter(Mandatory,Position=2)]
[ScriptBlock]$Code
)
process {
$OriginalCulture = Get-Culture
try
{
Set-Culture $CultureCode
Write-Verbose (Get-Culture) #this always returns en-GB
Invoke-Command -ScriptBlock $Code
}
finally
{
Set-Culture $OriginalCulture
}
}
}
The following code implies that this method works:
Invoke-CommandInCulture -CultureCode 'fr' -Code {
[System.Threading.Thread]::CurrentThread.CurrentUICulture
[System.Threading.Thread]::CurrentThread.CurrentCulture
} #shows that the command's thread's culture is French
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-date
} #returns the current date in French
However PowerShell has it's own idea of what's going on
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-culture
"PSCulture: $PSCulture"
"PSUICulture: $PSUICulture"
} #returns my default (en-GB) culture; not the thread's culture
And this impacts the logic for converting to CSV:
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-process | ConvertTo-CSV -UseCulture
} #again, uses my default culture's formatting rules; not the FR ones
Workaround #1: Custom Function to Convert Values to Strings in Given Culture
Here's a workaround solution; converting each field to a string using the given culture, then converting the string values to a CSV:
function ConvertTo-SpecifiedCulture {
[CmdletBinding()]
param (
[Parameter(Mandatory,ValueFromPipeline)]
[PSObject]$InputObject
,
[Parameter(Mandatory)]
[string]$CultureCode
,
[Parameter(ParameterSetName='DefaultParameter', Position=0)]
[System.Object[]]$Property
)
begin {
[System.Globalization.CultureInfo] $Culture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
}
process {
if($Property -eq $null) {$Property = $InputObject.PSStandardMembers.DefaultDisplayPropertySet.ReferencedPropertyNames}
$Result = new-object -TypeName PSObject -Property #{}
$Property | %{
$Result | Add-Member -MemberType NoteProperty -Name $_ -Value ($InputObject."$_").ToString($Culture)
}
$Result
}
}
Get-Process | select -first 2 | ConvertTo-SpecifiedCulture -CultureCode 'fr' | ConvertTo-CSV -Delimiter ';'
Workaround #2: Override Current Culture to Match Target Culture
Another option is to change the settings of the current culture so that it shares those of the required culture. This feels more hacky; though depending on scenario may work out cleaner/more practical than the above.
e.g. to use FR's number format we just update the current culture's number format to match FR's:
$(Get-Culture).NumberFormat = ([System.Globalization.CultureInfo]'FR').NumberFormat
...and we can do likewise for the remaining (settable) properties:
function Set-CurrentCulture {
[CmdletBinding()]
param (
[string]$CultureCode
)
begin {
$Global:FakedCurrentCulture = $CultureCode #in case we need a reference to the current culture's name
[System.Globalization.CultureInfo]$NewCulture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
[System.Globalization.CultureInfo]$ReferenceToCurrentCulture = Get-Culture
Write-Verbose "Switching Defintion to $($NewCulture.Name)"
}
process {
#NB: At time of writing, the only settable properties are NumberFormatInfo & DateTimeFormatInfo
$ReferenceToCurrentCulture.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property $propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture."$propertyName")"
write-verbose "- to: $($NewCulture."$propertyName")"
$ReferenceToCurrentCulture."$propertyName" = $NewCulture."$propertyName"
}
#ListSeparator
$ReferenceToCurrentCulture.TextInfo.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property TextInfo.$propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture.TextInfo."$propertyName")"
write-verbose "- to: $($NewCulture.TextInfo."$propertyName")"
$ReferenceToCurrentCulture.TextInfo."$propertyName" = $NewCulture."$propertyName"
}
#for some reason this errors
#Localized, TwoDigitYearMax
<#
$ReferenceToCurrentCulture.Calendar.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property Calendar.$propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture.Calendar."$propertyName")"
write-verbose "- to: $($NewCulture.Calendar."$propertyName")"
$ReferenceToCurrentCulture.Calendar."$propertyName" = $NewCulture."$propertyName"
}
#>
}
}
function Reset-CurrentCulture {
[CmdletBinding()]
param ()
process {
Set-CurrentCulture -CultureCode ((get-culture).Name)
}
}
function Test-It {
[CmdletBinding()]
param ()
begin {
write-verbose "Current Culture: $Global:FakedCurrentCulture"
}
process {
1..5 | %{
New-Object -TypeName PSObject -Property #{
Integer = $_
String = "Hello $_"
Numeric = 2.139 * $_
Money = (2.139 * $_).ToString('c')
Date = (Get-Date).AddDays($_)
}
} | ConvertTo-Csv -NoTypeInformation
}
}
Set-CurrentCulture 'fr' -Verbose
Test-It
Set-CurrentCulture 'en-GB' -Verbose
Test-It
Set-CurrentCulture 'en-US' -Verbose
Test-It
Set-CurrentCulture 'ar-DZ' -Verbose
Test-It
Reset-CurrentCulture -Verbose
Test-It
We could potentially go further and look at overwriting the read only properties (this is possible: https://learn-powershell.net/2016/06/27/quick-hits-writing-to-a-read-only-property/)... but this already feels very nasty; so I'm not going to go there as the above was sufficient for my needs.

What is an equivalent of *Nix 'cut' command in Powershell?

I have following content in a configuration file (sample.cfg),
Time_Zone_Variance(Mins):300
Alert_Interval(Mins):2
Server:10.0.0.9
Port:1840
I'm trying to store an each values after the : by using split in PowerShell. but i'm not able to produce require output.
Can someone tell me how to use PowerShell split for the above problem ?
You can read the contents of the file using Get-Content, then pipe each line through ForEach-Object, then use the split command on each line, taking the second item in the array as follows:
$filename = "sample.cfg"
Get-Content $filename | ForEach-Object {
$_.split(":")[1]
}
Output
300
2
10.0.0.9
1840
Update
I prefer the approach by #AnsgarWiechers, but if you really need specifically named values you could create a hashtable and replace the name with the value:
$configValues = #{
hour = "Time_Zone_Variance(Mins)"
min = "Alert_Interval(Mins)"
server = "Server"
port = "Port"
}
Get-Content $filename | ForEach-Object {
# Courtesy of Ansgar Wiechers
$key, $value = $_ -split ':', 2
foreach($configValuesKey in $($configValues.keys)) {
if ($configValues[$configValuesKey] -eq $key)
{
$configValues[$configValuesKey] = $value
}
}
}
write-host "`nAll Values:"
$configValues
write-host "`nIndividual value:"
$configValues.port
Output
All Values:
Name Value
---- -----
port 1840
min 2
server 10.0.0.9
hour 300
Individual value:
1840
How's this?
function cut {
param(
[Parameter(ValueFromPipeline=$True)] [string]$inputobject,
[string]$delimiter='\s+',
[string[]]$field
)
process {
if ($field -eq $null) { $inputobject -split $delimiter } else {
($inputobject -split $delimiter)[$field] }
}
}
PS C:\> 'hi:there' | cut -f 0 -d :
hi
PS C:\> 'hi:there' | cut -f 1 -d :
there
PS C:\> 'hi:there' | cut -f 0,1 -d :
hi
there
PS C:\> 'hi:::there' | cut -f 0 -d :+
hi
PS C:\> 'hi there' | cut
hi
there
For a more succint syntax, this will also do the trick:
((Get-Content "your-file.txt") -Split ":")[1]
So the trick to use the -Split method is to have a String object returned by Get-Content (alias cat can also be used, actually), and from the resulting String[] object you can use the brackets to extract the nth item.
Note: Using -Split without parenthesis around Get-Content won't work since -Split is not a parameter name for that command... 🤷‍♂️
I suppose you don't want to just split the lines, but actually create key/value pairs. That could be achieved like this:
$config = #{}
Get-Content 'C:\path\to\sample.cfg' | % {
$key, $value = $_ -split ':', 2
$config[$key] = $value
}
You could also use the ConvertFrom-StringData cmdlet:
Get-Content 'C:\path\to\sample.cfg' | % {
ConvertFrom-StringData ($_ -replace ':','=')
}
The -replace operation is necessary, because ConvertFrom-StringData expects key and value to be separated by =. If you could change the delimiter in the config file from : to =, you could use ConvertFrom-StringData $_ without replacement.

Script to export to excel

I Have below script:-- looking for help to convert the output to excel format
$servers = get-content “c:\list.txt”
foreach ($server in $servers)
{
$server
$command = “quser /server:” + $server
invoke-expression $command
}
when executed getting in below format the output.
server1
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
Vdw231 ica-tcp#8 7 Active . 11/5/2012 10:40 AM
Vdw232 ica-tcp#60 16 Active 16:18 11/5/2012 2:22 PM
Vdw233 ica-tcp#71 3 Active . 11/6/2012 6:10 AM
Vdw234 ica-tcp#72 1 Active 3 11/6/2012 6:59 AM
Vdw235 ica-tcp#73 5 Active . 11/6/2012 6:59 AM
Vdw236 rdp-tcp#74 2 Active . 11/6/2012 7:07 AM
server2
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
Vdw210 ica-tcp#44 14 Active 13:50 11/5/2012 9:03 AM
Vdw211 ica-tcp#67 6 Active . 11/6/2012 1:56 AM
Vdw212 ica-tcp#70 1 Active 45 11/6/2012 6:34 AM
Vdw213 ica-tcp#72 9 Active 25 11/6/2012 6:53 AM
Vdw214
server3
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
Vdw215 rdp-tcp#131 1 Active 19 11/5/2012 1:42 AM
Vdw216 rdp-tcp#132 4 Active 17 11/5/2012 2:06 AM
Vdw217 rdp-tcp#143 6 Active . 11/6/2012 3:31 AM
My requirement is i wanted to convert this output to excel format for submitting to management. Below is the excel format that i am thinking...to have from above script...
I've rewritten this, but I didn't test the full script and it's not optimized. If you encounter any
problems, feel free to contact me.
$statuses = #()
$servers = get-content "c:\list.txt"
$splitter = [regex]"\s+"
foreach ($server in $servers)
{
$command = "quser /server:$server"
$lines = #((invoke-expression $command | Out-String) -split "`n")
#remove header
$lines = $lines[1..$lines.count]
foreach ($line in $lines)
{
$attrs = #($splitter.Split($line.Trim(),6))
if ( $attrs -eq 6 )
{
$status = New-Object PSCustomObject -Property #{
"SERVER"=$server;
"USERNAME"=$attrs[0];
"SESSIONNAME"=$attrs[1];
"ID"=$attrs[2];
"STATE"=$attrs[3];
"IDLE_TIME"=$attrs[4];
"LOGON_TIME"=[datetime]$attrs[5]}
$statuses += $status
}
}
}
#your filter here
#$statuses = $statuses | where{ XXXXX }
$statuses | Export-Csv G:/test.csv -NoTypeInformation
You need to convert PSObject to an excel compatible Array and after you can write this array in excel sheet
include this code in your *.PS1 script, and use like this : get-process | Export-Excel
#=============================================================================
# Convert powershell Object to Array for Excel
#=============================================================================
function ConvertTo-MultiArray {
<#
.Notes
NAME: ConvertTo-MultiArray
AUTHOR: Tome Tanasovski
Website: http://powertoe.wordpress.com
Twitter: http://twitter.com/toenuff
Version: 1.2
.Synopsis
Converts a collection of PowerShell objects into a multi-dimensional array
.Description
Converts a collection of PowerShell objects into a multi-dimensional array. The first row of the array contains the property names. Each additional row contains the values for each object.
This cmdlet was created to act as an intermediary to importing PowerShell objects into a range of cells in Exchange. By using a multi-dimensional array you can greatly speed up the process of adding data to Excel through the Excel COM objects.
.Parameter InputObject
Specifies the objects to export into the multi dimensional array. Enter a variable that contains the objects or type a command or expression that gets the objects. You can also pipe objects to ConvertTo-MultiArray.
.Inputs
System.Management.Automation.PSObject
You can pipe any .NET Framework object to ConvertTo-MultiArray
.Outputs
[ref]
The cmdlet will return a reference to the multi-dimensional array. To access the array itself you will need to use the Value property of the reference
.Example
$arrayref = get-process |Convertto-MultiArray
.Example
$dir = Get-ChildItem c:\
$arrayref = Convertto-MultiArray -InputObject $dir
.Example
$range.value2 = (ConvertTo-MultiArray (get-process)).value
.LINK
http://powertoe.wordpress.com
#>
param(
[Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
[PSObject[]]$InputObject
)
BEGIN {
$objects = #()
[ref]$array = [ref]$null
}
Process {
$objects += $InputObject
}
END {
$properties = $objects[0].psobject.properties |%{$_.name}
$array.Value = New-Object 'object[,]' ($objects.Count+1),$properties.count
# i = row and j = column
$j = 0
$properties |%{
$array.Value[0,$j] = $_.tostring()
$j++
}
$i = 1
$objects |% {
$item = $_
$j = 0
$properties | % {
if ($item.($_) -eq $null) {
$array.value[$i,$j] = ""
}
else {
$array.value[$i,$j] = $item.($_).tostring()
}
$j++
}
$i++
}
$array
}
}
#=============================================================================
# Export pipe in Excel file
#=============================================================================
function Export-Excel {
[cmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
[PSObject[]]$InputObject
)
begin{
$header=$null
$row=1
$xl=New-Object -ComObject Excel.Application
$wb=$xl.WorkBooks.add(1)
$ws=$wb.WorkSheets.item(1)
$xl.Visible=$false
$xl.DisplayAlerts = $false
$xl.ScreenUpdating = $False
$objects = #()
}
process{
$objects += $InputObject
}
end{
$array4XL = ($objects | ConvertTo-MultiArray).value
$starta = [int][char]'a' - 1
if ($array4XL.GetLength(1) -gt 26) {
$col = [char]([int][math]::Floor($array4XL.GetLength(1)/26) + $starta) + [char](($array4XL.GetLength(1)%26) + $Starta)
} else {
$col = [char]($array4XL.GetLength(1) + $starta)
}
$ws.Range("a1","$col$($array4XL.GetLength(0))").value2=$array4XL
$wb.SaveAs("$([Environment]::GetFolderPath('desktop'))\Export-Excel ($(Get-Date -Format u)).xlsx")
$xl.Quit()
Remove-Variable xl
}
}
you get