Unable to create two-way table in excel from powershell - excel

I want to create two way table in excel by exporting object from powershell. I am able to create a table in powershell.
The code as shown below:
class sampleClass {
[String] $var1
[String] $var2
[Bool] $boolVar
sampleClass([String] $var1, [String] $var2, [Bool] $boolVar)
{
$this.var1 = $var1
$this.var2 = $var2
$this.boolVar = $boolVar
}
[String] ToString()
{
return $this.var1 + ": " + $this.var2 + ": " + $this.boolVar
}
}
$s1 = [sampleClass]::new("Comp1", "S1", $false)
$s2 = [sampleClass]::new("Comp2", "S2", $true)
$s3 = [sampleClass]::new("Comp1", "S2", $false)
$s4 = [sampleClass]::new("Comp2", "S1", $false)
$s = #()
$s += $s1
$s += $s2
$s += $s3
$s += $s4
$s | Export-Csv .\out.csv -NoTypeInformation
The output for above code is as shown below:
But the output that I want is not that, but as shown below:
Kindly help.

This code may be your best bet:
function Transpose-Object
{ [CmdletBinding()]
Param([OBJECT][Parameter(ValueFromPipeline = $TRUE)]$InputObject)
BEGIN
{ # initialize variables just to be "clean"
$Props = #()
$PropNames = #()
$InstanceNames = #()
}
PROCESS
{
if ($Props.Length -eq 0)
{ # when first object in pipeline arrives retrieve its property names
$PropNames = $InputObject.PSObject.Properties | Select-Object -ExpandProperty Name
# and create a PSCustomobject in an array for each property
$InputObject.PSObject.Properties | %{ $Props += New-Object -TypeName PSObject -Property #{Property = $_.Name} }
}
if ($InputObject.Name)
{ # does object have a "Name" property?
$Property = $InputObject.Name
} else { # no, take object itself as property name
$Property = $InputObject | Out-String
}
if ($InstanceNames -contains $Property)
{ # does multiple occurence of name exist?
$COUNTER = 0
do { # yes, append a number in brackets to name
$COUNTER++
$Property = "$($InputObject.Name) ({0})" -f $COUNTER
} while ($InstanceNames -contains $Property)
}
# add current name to name list for next name check
$InstanceNames += $Property
# retrieve property values and add them to the property's PSCustomobject
$COUNTER = 0
$PropNames | %{
if ($InputObject.($_))
{ # property exists for current object
$Props[$COUNTER] | Add-Member -Name $Property -Type NoteProperty -Value $InputObject.($_)
} else { # property does not exist for current object, add $NULL value
$Props[$COUNTER] | Add-Member -Name $Property -Type NoteProperty -Value $NULL
}
$COUNTER++
}
}
END
{
# return collection of PSCustomobjects with property values
$Props
}
}
It will allow you to turn you columns into rows and then export the object. Use like this:
$s | Transpose-Object | Export-Csv .\out.csv -NoTypeInformation

Related

Powershell - Convert list to UI with Out-Gridview

I have a script that read from excel and let the user to choose a column. The issue is that the list is not readable and I want to show the user the option to choose the version with UI with Out-Gridview
One more thing, I need that the answer will be a number
Here is the script:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
$columns = [ordered]#{}
for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
if ($name -ne $null){
$columns[$name] = $col}
}
$columns.GetEnumerator() | ForEach-Object {
# {0,2} means to write the index number from $_.Value right aligned for two digits
'{0,2}: {1}' -f $_.Value, $_.Name
}
do {
$answer = Read-Host "Please enter the number of the column you want to read from" #. Press Q to exit
# ask this question until the user enters a number or 'Q'
} until ($answer -eq 'Q' -or $answer -match '^\d{1,2}$')
switch ($answer) {
'Q' { break } # user wants to quit
{1..$columns.Count} {
# get the Name from the chosen value
$action = $columns.Keys | Where-Object {$columns["$_"] -eq $answer}
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
}
It looks like this:
To let the user select the tool version using Out-GridView, you need to build an array of objects, like below:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
# now, have the loop output objects that will be collected in variable $columns
$columns = for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
# if $name is not empty or whitespace only
if ($name -match '\S') {
[PsCustomObject]#{
Number = $col
Version = $name
}
}
}
# output to Out-GridView with -PassThru parameter so you can capture the selected item
$answer = ($columns | Out-GridView -Title 'Please select' -PassThru).Number
# if the user did not cancel
if ($answer) {
# get the Name from the chosen value
$action = $columns[$answer -1].Version
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
Please do not forget to remove the used COM objects from memory when the code is done, otherwise they will linger on..
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkSheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkBook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelObject)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Two objects of different sizes and $null

My code below works in every instance except for if one object is $null and the other object has one item. When that situation occurs the output becomes 1 letter like it is indexing and I am not sure why.
How do I combine the two objects to make a final report?
$ADGroups = Get-ADPrincipalGroupMembership -Identity $UserSam | Select-Object distinguishedName, name | Where-Object { ($_.distinguishedName -ne 'CN=Domain Users,CN=Users,DC=com') }
#record AD groups
$ADResult = #()
if ($null -eq $ADGroups) {
Write-Warning "No AD Groups"
$ADResult = [PSCustomObject]#{
ADGroups = #()
ADGroupsdistinguishedName = #()
}
}
Else {
$ADResult = $ADGroups | ForEach-Object {
[PSCustomObject]#{
ADGroups = $_.name
ADGroupsdistinguishedName = $_.distinguishedName
}
}
}
#============= Now Google, get user groups and record
$GoogleGroups = gam print groups member $email members managers owners | ConvertFrom-Csv
# Record Google Groups
$GResult = #()
If ($null -eq $GoogleGroups) {
Write-Warning "No Google Groups"
$GResult = [PSCustomObject]#{
GoogleGroups = #()
Role = #()
}
}
Else {
$group = $null
$GResult = ForEach ($group in $GoogleGroups) {
#this records what role the user had in the group(s)
$GoogleMember = gam print group-members group $group.email members | ConvertFrom-Csv | Select-Object -ExpandProperty email
$Role = $null
If ( $GoogleMember -contains $EMAIL) {
$Role = 'Member'
}
Else {
$GoogleManager = gam print group-members group $group.email managers | ConvertFrom-Csv | Select-Object -ExpandProperty email
If ($GoogleManager -contains $EMAIL) {
$Role = 'Manager'
}
Else {
$Role = 'Owner'
}
}
[PSCustomObject]#{
GoogleGroups = $group.email
Role = $role
}
$group = $null
}
}
# ---------now report that will be dropped off at end
[int]$max = $ADResult.count
if ([int]$GResult.count -gt $max) { [int]$max = $GResult.count }
If ($max -eq 1 -or $max -eq 0) {
$Result = [PSCustomObject]#{
PrimaryEmail = $email
Title = $UserInfo.title
Department = $UserInfo.Department
Manager = $Manager
ADGroupName = $ADResult.ADGroups
ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName
GoogleGroup = $GResult.GoogleGroups
Role = $GResult.role
DateOfSeparation = (Get-Date).ToString("yyyy_MM_dd")
UserDistinguishedName = $UserInfo.distinguishedName
UserOU = $UserInfo.Ou
PrimaryGroup = $UserInfo.primaryGroup.Split('=').Split(',')
}
}
Else {
$Result = for ( $i = 0; $i -lt $max; $i++) {
[PSCustomObject]#{
PrimaryEmail = $email
Title = $UserInfo.title
Department = $UserInfo.Department
ADGroupName = $ADResult.ADGroups[$i]
ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName[$i]
GoogleGroup = $GResult.GoogleGroups[$i]
Role = $GResult.role[$i]
DateOfSeparation = (Get-Date).ToString("yyyy_MM_dd")
UserDistinguishedName = $UserInfo.distinguishedName
UserOU = $UserInfo.Ou
PrimaryGroup = $UserInfo.primaryGroup.Split('=').Split(',')[$i]
}
}
}
$Result | Export-Csv 'C:\temp\Groups.csv' -NoTypeInformation
Going by the abstract description of your problem:
You're seeing an unfortunate asymmetry in PowerShell:
In the pipeline, a [string] instance is considered a single object.
PS> ('foo' | Measure-Object).Count
1
With respect to indexing, it is considered an array of characters.
PS> 'foo'[0]
f
A general feature of capturing a PowerShell pipeline's output is that if a command situationally outputs just a single object, that object is captured as-is, whereas two or more output objects result in a regular PowerShell array, of type [object[]].
Typically, this isn't a problem, because PowerShell's unified handling of scalars and collections allows you to index even into a scalar (single object), i.e. to implicitly treat a single object as if it were a single-element array:
PS> (Write-Output 42, 43)[0]
42
PS> (Write-Output 42)[0]
42 # still OK, even though only *one* object was output; same as: (42)[0]
However, with a single [string] instance as the output it becomes a problem, for the reasons stated above:
PS> (Write-Output 'foo', 'bar')[0]
foo # OK
PS> (Write-Output 'foo')[0]
f # !! Indexing into a *single string* treats it as *character array*
The same applies to values returned via member-access enumeration, perhaps surprisingly :
PS> (Get-Item $HOME, /).FullName[0]
C:\Users\Jdoe
PS> (Get-Item $HOME).FullName[0]
C # !! Indexing into a *single string* treats it as *character array*
Workarounds:
Enclose the command of interest in #(...), the array-subexpression operator so as to ensure that its output is always considered an array.
PS> #(Write-Output 'foo')[0]
foo # OK
Alternatively, when capturing a command's output in a variable, type-constrain that variable to [array] (same as [object[]]) or a strongly typed array, [string[]]:
PS> [array] $output = Write-Output 'foo'; $output[0]
foo # OK

PowerShell: Working with Strings and Hashtables

I have this code:
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
Write-Host "set $pass ="$passwordsTable[$pass]""
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
# Add-Content "d:\myFile.txt" "set $pass ="$passwordsTable[$pass]""
Which perfectly works when I use Write-Host. But I want to do something like in the comment in line 25. I tried several ways but I always got a static string instead of the values that I get from the Hashtable.
At the end I want to have something like:
set pass1 = 12
set pass2 = 5
in myFile.txt
Any help is appreciated. Thanks!
You could change Write-Host (just prints to a console) to Write-Output ( which passes an object to a pipeline). Write-Output does not print to the console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
$counter=0
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
$counter++
Write-Output "set "pass$counter = $passwordsTable[$pass]"`n" | Add-Content -NoNewline myFile.txt
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
Output:
set pass1=10
set pass2=13
set pass3=12
You can replace the first foreach loop if you simply replace all the &&& by a newline and use cmdlet ConvertFrom-StringData.
Add-Content also has a switch called -PassThru that will let you write to the file and also output to console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$passwordsTable = $passwordsParameters -replace '&&&', [Environment]::NewLine | ConvertFrom-StringData
foreach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass]) {
$msg = 'set {0} = {1}' -f $pass, $passwordsTable[$pass]
# or use: $msg = "set $pass = $($passwordsTable[$pass])"
# write this to the file. switch -PassThru will also output to the console
Add-Content -Path 'D:\myFile.txt' -Value $msg -PassThru
} else {
Write-Host "A value for the parameter '$pass' was not entered."
}
}

Replacing multiple texts not getting saved

I've a bunch of files in which I need to replace content like for e.g. wherever there is 'AA' I need to replace with 'E1', 'A1' with 'P4'. The same content needs to be changed differently in different files. So for example in the 2nd file 'AA' would become 'P1', 'A1' would become 'E1', etc. To accomplish this I've an Excel sheet with 2 columns like the below:
TC CodeChange
086 AA-E1; A1-P2
099 AA-P2; A1-E1; A2-E2; Z3-E3
100 AA-P2; A1-E2; A2-E3; Z3-O3
PowerShell script which I wrote for the above:
Script 1:
function func3 {
Param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output = $_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\...\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")) {
(Get-Content $inputfile.FullName) | ForEach-Object {
$_ -replace "$($arry1[$j].Split('-')[0])","$($arry1[$j].Split('-')[1])"
} | Set-Content $inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
Script 2:
function func3 {
param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output=$_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).Count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")){
$content = [System.IO.File]::ReadAllText($inputfile.FullName).Replace($arry1[$j].Split('-')[0], $arry1[$j].Split('-')[1])
[System.IO.File]::WriteAllText($inputfile.FullName, $content)
Write-Host $arry1[$j].Split('-')[0]' replaced with '$arry1[$j].Split('-')[1]' in file: '$inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
The folder where the files are has the files having names containing the same digits in the 'TC' column in my Excel sheet. Example:
TC 086.txt
TC 099.txt
etc.
That way after I import the contents of the Excel into a hashtable I extract the digits from the filenames and get the corresponding values for the same key in the hashtable. For example the value for the key '086' from the hashtable would be 'AA-E1; A1-P2'. Then I split the items to be replaced from the hashtable value (separated by ;) and then store that in an array. The using a loop I try to replace the contents of each file based on the data retrieved from the spreadsheet.
The issue I'm facing with both the approaches is that only the 1st item in each file is getting replaced. The rest of the items are not getting replaced. For example only 'AA' value in file 'TC 086.txt' is getting replaced with 'E1'. 'A1' is not getting replaced with 'P2'.
I found out what the issue was. I basically had to trim the elements of the array
$arry1
after splitting them (separated by ;) and before passing them as parameters to the 'Replace' function. Apparently there was a space before every element in that array except the 1st element (that's how they were stored in the source: excel spreadsheet). Hence the 'Replace' method was not finding that element in the file and hence not replacing it. Removing the spaces before the elements solved the issue

Powershell filter a List by Name and Date

I need a bit of help... I'm new to powershell and i want to Filter a List (csv). I would love to remove all lines with certain names in it. and cut the list down to the last month. In the script you can see how far i got till now.
param(
[Parameter(ValueFromPipeline=$true,HelpMessage="Enter CSV path(s)")]
[String[]]$Path = $null
)
if($Path -eq $null) {
Add-Type -AssemblyName System.Windows.Forms
$Dialog = New-Object System.Windows.Forms.OpenFileDialog
$Dialog.InitialDirectory = "$InitialDirectory"
$Dialog.Title = "Select CSV File(s)"
$Dialog.Filter = "CSV File(s)|*.csv"
$Dialog.Multiselect=$true
$Result = $Dialog.ShowDialog()
if($Result -eq 'OK') {
Try {
$Path = $Dialog.FileNames
}
Catch {
$Path = $null
Break
}
}
else {
Write-Host -ForegroundColor Yellow "Notice: No file(s) selected."
Break
}
}
$info=Import-Csv "$path" -Delimiter ';'
$info | Get-Member
$info | Format-Table
as you can see i tryed to link the path to a filebrowser.
For the purposes of discussion, I will assume that the full pathname of the CSV is in the variable $InputPath, and that you want to write the result to a CSV file whose full pathname is in the variable $OutputPath. I will also assume that the CSV file contains a column named 'Name', and that the value from the Name column that you want to exclude is in the variable $ExcludedName. Given that, you can simply do
Import-CSV -Path $InputPath | Where-Object {$_.Name -ne $ExcludedName} | Export-CSV -Path $OutputPath -NoTypeInformation
You can do this by my code,but dont forget that first row must contains names of column and delimiter must be ';' and $nameslist is array of names that you need delete:
$info=Import-Csv "D:\testdir\file2.csv" -Delimiter ';'
$nameslist=#('James','John','andrew')
foreach($i in $info){
if($nameslist -contains $i.Name){
$i.Name=""
}
$i|Export-Csv -Path "D:\testdir\file1.csv" -Delimiter ';' -NoTypeInformation -Force -Encoding UTF8 -Append
}
Try this:
$data = Import-Csv "Path" | Select-Object * -ExcludeProperty Names
$data | export-csv "Path" -Notype
This will cut the column names.
Try it first without using a function:
Import-Csv <Filename> | Where-Object {$_.<FieldName> -notlike "*<Value>*"}
Also, you might consider something like this:
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, HelpMessage = "Enter CSV path(s)")]
[String[]]$Path = $(
Add-Type -AssemblyName System.Windows.Forms
$DialogProperties = #{
Title = 'Select CSV File(s)'
Filter = 'CSV File(s)|*.csv'
Multiselect = $True
}
$Dialog = New-Object System.Windows.Forms.OpenFileDialog -Property $DialogProperties
$Dialog.ShowDialog()
If ($Result -eq 'OK') {
$Path = $Dialog.FileNames
} Else {
Write-Error 'Notice: No file(s) selected.'
}
)
)
Process {
ForEach ($PathItem in $Path) {
Import-Csv $PathItem | Where-Object { $_.Name -notlike "*NotThisOne*" }
}
}

Resources