how to read unique values from excel column using powershell - powershell

i would like to read unique email id from a column and assign to a local variable, can anyone assist in doing that
my data will be like
i would like to get unique values from the excel and assign it to a variable using power shell
the variable should hold value in following way Nalin23#bridgestone.com;raj#bridgestone.com;kishan#bridgestone.com

To read the values from an Excel column and return it as array of values, you can use this helper function:
function Import-ExcelColumn {
# returns an array of Excel Column values
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $false, Position = 1)]
[int]$WorkSheetIndex = 1,
[Parameter(Mandatory = $false, Position = 2)]
[int]$ColumnIndex = 1
)
# constants from https://learn.microsoft.com/en-us/office/vba/api/excel.xldirection
$xlDown = -4121
$xlUp = -4162
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open($Path)
$worksheet = $workbook.Worksheets.Item($WorkSheetIndex)
# get the first and last used row indices
$firstRow = $worksheet.Cells($worksheet.UsedRange.Rows.Count, 1).End($xlUp).Row
$lastRow = $worksheet.Cells($firstRow, 1).End($xlDown).Row
# collect the values in this column in variable $result
# start at $firstRow + 1 to skip the header itself
$result = for ($row = $firstRow + 1; $row -le $lastRow; $row++) {
$worksheet.Cells.Item($row, $ColumnIndex).Value2
}
$excel.Quit()
# IMPORTANT: clean-up used Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($worksheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
# $result is an array. PowerShell 'unravels' arrays when returned from a function.
# to overcome this, prefix the returned array with a unary comma.
return ,$result
}
After that, in your case use it like this:
$emailAddresses = ((Import-ExcelColumn -Path 'D:\Test\Map1.xlsx' -ColumnIndex 2) | Select-Object -Unique) -join ';'
to get a string:
Nalin23#bridgestone.com;raj#bridgestone.com;kishan#bridgestone.com

Please show the code you have attempted as a reference for everyone answering the qustion.
With that said, the below code should work for a comma separate value (.csv) file:
# Get CSV object
$csv_object = Import-CSV $path_to_csv
# Find unique entries from email_id column
$unique_emails = $csv_object.email_id | Select -Unique
# Join them with ;
$delim_emails = $unqiue_emails -join ";"

Related

Powershell pass parameter to function to query subproperty of object [duplicate]

This question already has answers here:
Set Value of Nested Object Property by Name in PowerShell
(3 answers)
Access PSObject property indirectly with variable
(3 answers)
Closed 1 year ago.
What im trying to do is getting one specific value from nested JSON. Using array keys as expression.
Array with keys and values:
$AccountService = #{
'root.branch.setting1'= 'Val1'
'root.branch.setting2'= 'Val2'
'root.branch.setting3'= 'Val3'
}
Create JSON Object
$json = Get-Content 'C:\Users\ramosfer\Documents\test.json' | ConvertFrom-Json
Get every key from array using a loop to get the value from the JSON. Expecting something like this in the Expression ($json.root.branch.setting1)
$AccountService.GetEnumerator() | % {
$json | Select-Object #{Name="Val"; Expression={$json.$_}}
}
Use this $json.$_ and expect something like this
Val
---
Val1
Val2
Val3
The best way of resolving nested properties is to resolve them one at a time :)
A simpler example, for retrieving just one value:
$json = '{"root":{"branch":{"setting1":"Value 1","setting2":"Value 2","setting3":"Value 3"}}}' |ConvertFrom-Json
# define the "path" to the value we want
$path = 'root.branch.setting1'
# split path into individual property names
$path = $path.Split('.')
# start from the root object and then start "walking" down the object hierarchy
$cursor = $json
foreach($property in $path)
{
# advance cursor one level deeper
$cursor = $cursor.$property
}
# this will now contain "Value 1"
$cursor
You can turn this into a neat little function:
function Resolve-MemberChain
{
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[psobject[]]$InputObject,
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$MemberPath,
[Parameter(Mandatory = $false)]
[string]$Delimiter
)
begin {
if($PSBoundParameters.ContainsKey('Delimiter')){
$MemberPath = $MemberPath.Split([string[]]#($Delimiter))
}
}
process {
foreach($obj in $InputObject){
$cursor = $obj
foreach($member in $MemberPath){
$cursor = $cursor.$member
}
$cursor
}
}
}
Then use it like this:
$json |Resolve-MemberChain 'root.branch.setting1' -Delimiter '.'
Or, as in your case, within a calculated property expression, like so:
$AccountService.GetEnumerator()|%{
$path = $_.Key
$json |Select #{Name='Val';Expression={$_ |Resolve-MemberChain $path -Delimiter .}}
}

Why does the String show length 1?

In the following code, I am reading value from an Excel, match it with another string and want to read the length of the variable. However it always shows the value 1...
Can somebody explain me why?
BR
$FilePath = "randompath"
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open($FilePath)
$Worksheet = $Workbook.Sheets.Item(1)
$zahl1 = $worksheet.Cells.Item(1, 1).Value2
$docpath | out-string
[string]$Test = $docpath
$Cut = 13
If($Test -match("LBIW")){
$postiitonLBIW=$Test.IndexOf("LBIW")
$result1=$Test.Substring($postiitonLBIW)
$result1=$Test.Split("\")
$zahl1 = $result1 -match "LBIW"
$zahl1 = $zahl1.length
write-host $zahl1
The issue is $zahl1 is set to $true or $false, which is a length of 1.
$zahl1 = $result1 -match "LBIW"
$zahl1 = $zahl1.length
If you want the matches, you have to use $matches[n]. The way you are assigning $zahl1 is the output whether a match was found or not.
The better way:
$zahl1Len = 0 #default var incase no matches
if ($result1 -match "LBIW") { # if it has a match, process
$zahl1 = $matches[1] # or whatever match index
$zahl1Len = $zahl1.length
}
This way it is assigned the first match and counts the length, if it was matched.

How can i split up the results of this hashtable search?

I'm trying to use this to compare my AD NT hashdump with https://haveibeenpwned.com/Passwords hashes.
I'm having trouble with the results grouping multiple usernames with the same password together.
the code:
param(
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $ADNTHashes,
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $HashDictionary
)
#>
process {
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
#Declare and fill new hashtable with ADNThashes. Converts to upper case to
$htADNTHashes = #{}
Import-Csv -Delimiter ":" -Path $ADNTHashes -Header "User","Hash" | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)}
#Create empty output object
$mrMatchedResults = #()
#Create Filestream reader
$fsHashDictionary = New-Object IO.Filestream $HashDictionary,'Open','Read','Read'
$frHashDictionary = New-Object System.IO.StreamReader($fsHashDictionary)
#Iterate through HashDictionary checking each hash against ADNTHashes
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$foFoundObject = [PSCustomObject]#{
User = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
$stopwatch.Stop()
Write-Verbose "Function Match-ADHashes completed in $($stopwatch.Elapsed.TotalSeconds) Seconds"
}
end {
$mrMatchedResults
}
}
I tried commenting out | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)} which seems to be close, but that somehow removed the Frequency column.
The results look like this:
User Frequency Hash
---- --------- ----
{TestUser2, TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
I would like them separated:
User Frequency Hash
---- --------- ----
{TestUser2} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
i'm sure this is a simple change, but i have very little powershell experience.
The suggestion to change $FormatEnumerationLimit to -1 is not what i want either, that just fixes the list truncating.
{user1, user2, user3...}
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$Users = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
foreach($User in $Users){
$foFoundObject = [PSCustomObject]#{
User = $User
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
}

How to loop through arrays in hash table - passing parameters based on values read from a CSV file

Curious about how to loop through a hash table where each value is an array. Example:
$test = #{
a = "a","1";
b = "b","2";
c = "c","3";
}
Then I would like to do something like:
foreach ($T in $test) {
write-output $T
}
Expected result would be something like:
name value
a a
b b
c c
a 1
b 2
c 3
That's not what currently happens and my use case is to basically pass a hash of parameters to a function in a loop. My approach might be all wrong, but figured I would ask and see if anyone's tried to do this?
Edit**
A bit more clarification. What I'm basically trying to do is pass a lot of array values into a function and loop through those in the hash table prior to passing to a nested function. Example:
First something like:
$parameters = import-csv .\NewComputers.csv
Then something like
$parameters | New-LabVM
Lab VM Code below:
function New-LabVM
{
[CmdletBinding()]
Param (
# Param1 help description
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias("p1")]
[string[]]$ServerName,
# Param2 help description
[Parameter(Position = 1)]
[int[]]$RAM = 2GB,
# Param3 help description
[Parameter(Position=2)]
[int[]]$ServerHardDriveSize = 40gb,
# Parameter help description
[Parameter(Position=3)]
[int[]]$VMRootPath = "D:\VirtualMachines",
[Parameter(Position=4)]
[int[]]$NetworkSwitch = "VM Switch 1",
[Parameter(Position=4)]
[int[]]$ISO = "D:\ISO\Win2k12.ISO"
)
process
{
New-Item -Path $VMRootPath\$ServerName -ItemType Directory
$Arguments = #{
Name = $ServerName;
MemoryStartupBytes = $RAM;
NewVHDPath = "$VMRootPath\$ServerName\$ServerName.vhdx";
NewVHDSizeBytes = $ServerHardDriveSize
SwitchName = $NetworkSwitch;}
foreach ($Argument in $Arguments){
# Create Virtual Machines
New-VM #Arguments
# Configure Virtual Machines
Set-VMDvdDrive -VMName $ServerName -Path $ISO
Start-VM $ServerName
}
# Create Virtual Machines
New-VM #Arguments
}
}
What you're looking for is parameter splatting.
The most robust way to do that is via hashtables, so you must convert the custom-object instances output by Import-Csv to hashtables:
Import-Csv .\NewComputers.csv | ForEach-Object {
# Convert the custom object at hand to a hashtable.
$htParams = #{}
$_.psobject.properties | ForEach-Object { $htParams[$_.Name] = $_.Value }
# Pass the hashtable via splatting (#) to the target function.
New-LabVM #htParams
}
Note that since parameter binding via splatting is key-based (the hashtable keys are matched against the parameter names), it is fine to use a regular hashtable with its unpredictable key ordering (no need for an ordered hashtable ([ordered] #{ ... }) in this case).
Try this:
for($i=0;$i -lt $test.Count; $i++)
{$test.keys | %{write-host $test.$_[$i]}}
Weirdly, it outputs everything in the wrong order (because $test.keys outputs it backwards).
EDIT: Here's your solution.
Using the [System.Collections.Specialized.OrderedDictionary] type, you guarantee that the output will come out the same order as you entered it.
$test = [ordered] #{
a = "a","1";
b = "b","2";
c = "c","3";
}
After running the same solution code as before, you get exactly the output you wanted.

Table Cycling for Powershell

I'm creating a powershell script that I want to read a value (VALUE1) from an excel table (I can convert it to XML if necessary), assign it to a variable($PLACEHOLDER), run the rest of the script, then loop back to the beginning, but instead of reading the original value(VALUE1) I want it to read the value below it(VALUE2) and overwrite $PLACEHOLDER with VALUE2, then re-run the script until it returns a blank value, then I want it to stop. I am insanely new to powershell and it's interaction with excel/xml, so any help would be greatly appreciated. (I'm self-taught, so I don't know TOO much about parameters)
Sample in Terrible Psuedo:
#Initial placeholder value here
$RowNumber = 0
#Start of the loop here, add one to previous value
$RowNumber +1
#Call the value in Column (1), Row ($RowNumber), and assign it to $RowValue
?????? = $RowValue
#Execute the command involving the data value
ECHO "C:/test/temporary/$RowValue"
#Goto the start of the loop.
If you could be so kind, would you please give a quick explanation of the functions that you use (Parameters, what's happening, ect.)
EDIT: If it could detect and skip over blank rows, that would be amazing.
EDIT3: Code for Ansgar
$xl = New-Object -COM 'Excel.Application'
$xl.Visible = $true # set to $false for production
$wb = $xl.Workbooks.Open("C:\Documents and Settings\xe474109\Desktop\EXCEL FILES\testbook2.xlsx")
$ws = $wb.Sheets.Item(1)
$row = $ws.UsedRange.Rows.Count
while ( $ws.Cells.Item($row, 1).Value -ne $null ) {
$PLACEHOLDER = $ws.Cells.Item($row, 1).Value
#
# do stuff with $PLACEHOLDER here
#(I wanted to test this by just printing the $PLACEHOLDER value
$PLACEHOLDER
$row++
}
$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
Do you have Excel installed? If so, you can process Excel spreadsheets like this:
$xl = New-Object -COM 'Excel.Application'
$xl.Visible = $true # set to $false for production
$wb = $xl.Workbooks.Open('C:\path\to\your.xlsx')
$ws = $wb.Sheets.Item(1)
$row = $ws.UsedRange.Row
while ( $ws.Cells.Item($row, 1).Value -ne $null ) {
$PLACEHOLDER = $ws.Cells.Item($row, 1).Value
#
# do stuff with $PLACEHOLDER here
#
$row++
}
$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
cls
$csv = Import-csv -Path 'C:\test\csvStuff.csv'
foreach ($rec in $csv) {
if ($rec.nameofyourcolumn -ne '') {
& "c:\test\temporary\$($rec.nameofyourcolumn)"
}
}