Table Cycling for Powershell - 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)"
}
}

Related

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.

is there a simple way to output to xlsx?

I am trying to output a query from a DB to a xlsx but it takes so much time to do this because there about 20,000 records to process, is there a simpler way to do this?
I know there is a way to do it for csv but im trying to avoid that, because if the records had any comma is going to take it as a another column and that would mess with the info
this is my code
$xlsObj = New-Object -ComObject Excel.Application
$xlsObj.DisplayAlerts = $false
$xlsWb = $xlsobj.Workbooks.Add(1)
$xlsObj.Visible = 0 #(visible = 1 / 0 no visible)
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Count))
$xlsSh.Name = "QueryResults"
$DataSetTable= $ds.Tables[0]
Write-Output "DATA SET TABLE" $DataSetTable
[Array] $getColumnNames = $DataSetTable.Columns | SELECT *
Write-Output "COLUMN NAMES" $DataSetTable.Rows[0]
[Int] $RowHeader = 1
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName
Write-Output "Nombre de Columna"$ColH.ColumnName
$RowHeader++
}
[Int] $rowData = 2
[Int] $colData = 1
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
$xlsSh.Cells.NumberFormat = "#"
$xlsSh.Cells.Item($rowData, $colData) = $rec.$($Coln.ColumnName).ToString()
$ColData++
}
$rowData++; $ColData = 1
}
$xlsRng = $xlsSH.usedRange
[void] $xlsRng.EntireColumn.AutoFit()
#Se elimina la pestaña Sheet1/Hoja1.
$xlsWb.Sheets(1).Delete() #Versión 02
$xlsFile = "directory of the file"
[void] $xlsObj.ActiveWorkbook.SaveAs($xlsFile)
$xlsObj.Quit()
Start-Sleep -Milliseconds 700
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsRng)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsSh)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWb)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)) {''}
[gc]::collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null
$oraConn.Close()
I'm trying to avoid [CSV files], because if the records had any comma is going to take it as a another column and that would mess with the info
That's only the case if you try to construct the output format manually. Builtin commands like Export-Csv and ConvertTo-Json will automatically quote the values as necessary:
PS C:\> $customObject = [pscustomobject]#{ID = 1; Name = "Solis, Heber"}
PS C:\> $customObject
ID Name
-- ----
1 Solis, Heber
PS C:\> $customObject |ConvertTo-Csv -NoTypeInformation
"ID","Name"
"1","Solis, Heber"
Notice, in the example above, how:
The string value assigned to $customObject.Name does not contain any quotation marks, but
In the output from ConvertTo-Csv we see values and headers clearly enclosed in quotation marks
PowerShell automatically enumerates the row data when you pipe a [DataTable] instance, so creating a CSV might (depending on the contents) be as simple as:
$ds.Tables[0] |Export-Csv table_out.csv -NoTypeInformation
What if you want TAB-separated values (or any other non-comma separator)?
The *-Csv commands come with a -Delimiter parameter to which you can pass a user-defined separator:
# This produces semicolon-separated values
$data |Export-Csv -Path output.csv -Delimiter ';'
I usually try and refrain from recommending specific modules libraries, but if you insist on writing to XSLX I'd suggest checking out ImportExcel (don't let the name fool you, it does more than import from excel, including exporting and formatting data from PowerShell -> XSLX)

how to read unique values from excel column using 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 ";"

Powershell excel replace always returns true

I've doing a find and replace in a whole bunch of excel files (to update some links that have moved)
I want to track which files had changes made to them however the result of a replace is always true regardless of if the required text was is there. A find like this:
$worksheet.UsedRange.Find($Text)
will either return null if the text being looked for does not exist or an object if it does, but thats significant extra processing I'd prefer to remove given that I have about 1.2million files to check.
Here is my replace code:
$found=$false
$xlCalculationManual = -4135
$excel = New-Object -comobject Excel.Application
$excel.visible = $false
$excel.AskToUpdateLinks = $false
$excel.DisplayAlerts = $false
$excel.EnableEvents = $false
$workbook=$excel.Workbooks.Open($openfile,0,$false,5,"nopassword","nopassowrd")
$excel.Calculation = $xlCalculationManual
foreach ($worksheet in $workbook.Sheets)
{
foreach ($Text in $FindText)
{
If ($worksheet.UsedRange.replace($Text,$ReplaceText))
{
$found=$true
}
}
}
Looks like Theo is correct on this. I've not found anyway to increase the efficiency by removing the find.
Also it seems that on some files a replace only replaces the first instance and therefore a loop is also required.
Maybe try
$worksheet.UsedRange.replace($Text,$ReplaceText)
If ($?)
Instead of
If ($worksheet.UsedRange.replace($Text,$ReplaceText))
OR you could try searching for the text, and confirm that it found something?
Before your if replace option, try (I apologize, but this code is speculation, and may not full fix it. There is an article on this at https://msdn.microsoft.com/en-us/vba/excel-vba/articles/range-find-method-excel
$confirm = $worksheet.Used.Range.find($text)
if ($confirm -ne $null) {
Echo $true
$worksheet.UsedRange.replace($Text,$ReplaceText)
$confirm = $null
}
else{
echo $false
}

Retrieve data from PostgreSQL using Powershell

I have been wrestling with database connection to PostgreSQL from Powershell. I finally am able to connect to and insert into the database. Now I can't figure out how to extract data from a DB select into a variable.
I'm not including my insert for the sake of clarity but will tack it onto this thread later as I know it was super hard to find and may be helpful to someone.
so here's my code:
# use existing 64 bit ODBC System DSN that we set up manually
$DBconn = New-Object -comobject ADODB.Connection
$DBconn.Open("PostgreSQL35W")
$theQuery = "select * from test1"
$theObject = $DBconn.Execute($theQuery) # $theObject is a System.__ComObject
$numRecords = $theObject.RecordCount
write-host "found $numRecords records" # getting -1
$theObject.MoveFirst() # throws no error
# $theValue = $theObject.DataMember # throws no error, but gives no result
$theValue = $theObject.Index[1] # throws "Cannot index into a null array"
write-host($theValue)
try this
replace "#database#" with your database name in $cnString
replace "#server_ip#" with your server ip address in $cnString
replace "#user#" with a valid user in $cnString and $user
replace "#pass#" with a valid pass in $pass
replace "#table#" with a valid table name of your db
replace 5432 with your db port
$cnString = "DRIVER={PostgreSQL Unicode(x64)};DATABASE=#database#;SERVER=#server_ip#;PORT=5432;UID=#user#;"
$user="#user#"
$pass="#pass#"
$conn = New-Object -comobject ADODB.Connection
$conn.Open($cnString,$user,$pass)
$recordset = $conn.Execute("SELECT * FROM #table# limit 1;")
while ($recordset.EOF -ne $True)
{
foreach ($field in $recordset.Fields)
{
'{0,30} = {1,-30}' -f # this line sets up a nice pretty field format, but you don't really need it
$field.name, $field.value
}
'' # this line adds a line between records
$recordset.MoveNext()
}
$conn.Close();
Via psql, which comes with postgresql
$dburl="postgresql://exusername:expw#exhostname:5432/postgres"
$data="select * from extable" | psql --csv $dburl | ConvertFrom-Csv
You must have psql in your path or reference it, its within e.g. C:\Program Files\PostgreSQL\12\bin. Should be able to type "psql" and see output within powershell.
As a warning, expect strings. E.g $data[0].age.GetType() would be string, despite being stored in the database as an integer. You can immediately cast it, cast it later, or hope powershell infers type correctly.
If you want to add back in type information can do e.g.:
$data = $data | %{[pscustomobject]#{name=$_.name;age=[int]$_.age}}
I ended up figuring it out - here's what I did
$conn = New-Object -comobject ADODB.Connection
# use existing 64 bit ODBC System DSN that we set up manually
$conn.Open("PostgreSQL35W")
$recordset = $conn.Execute("SELECT * FROM JobHistory")
while ($recordset.EOF -ne $True)
{
foreach ($field in $recordset.Fields)
{
'{0,30} = {1,-30}' -f # this line sets up a nice pretty field format, but you don't really need it
$field.name, $field.value
}
'' # this line adds a line between records
$recordset.MoveNext()
}
$conn.Close();
Exit
use the dot notation. You don't need to split the data.
$list = New-Object Collections.Generic.List[OnlineCourse]
foreach($element in $results)
{
$tempObj= New-Object OnlineCourse($element.id,$element.courseName,$element.completedRatio,$element.completedRatio,$element.lastActivity, $element.provider)
$list.add($tempObj)
}
I have a slightly different approach to #dog, I couldn't get the --csv to work, so I resorted to tuple only rows returned, then parse them into a List of Classes (which happen to be called OnlineCourses):
class OnlineCourse
{
[int]$id
[string]$email
[string]$courseName
[int]$completedRatio
[datetime]$lastActivity
[String]$provider
OnlineCourse([int]$id,
[string]$email,
[string]$courseName,
[int]$completedPerc,
[datetime]$lastActivity,
[String]$provider) {
$this.id = $id
$this.email = $email.Trim()
$this.courseName = $courseName.Trim()
$this.completedRatio = $completedPerc
$this.lastActivity = $lastActivity
$this.provider = $provider.Trim()
}
}
$connstr="postgresql://exusername:expw#exhostname:5432/postgres"
$data = "select * from onlinecourses" | .\psql -t $connstr
$list = New-Object Collections.Generic.List[OnlineCourse]
foreach ($field in $data) {
$id, $email, $courseName, $completedratio, $lastactivity, $provider = $field.split('|')
$course = [OnlineCourse]::new($id, $email, $courseName, $completedratio, $lastactivity, $provider)
$list.Add($course)
}
This is slightly adapted from another answer and it worked for me.
$dburl="postgresql://postgres:secret_pwd#database-host:5432/dbname"
$psqlPath = 'C:\Program Files\PostgreSQL\11\bin\psql.exe'
function Query {
param($Sql)
Write-Host $Sql
$rows = $Sql `
| &$psqlPath "-A" $dburl | ConvertFrom-Csv -Delimiter '|'
$result = #($rows | Select-Object -SkipLast 1)
Write-Host "-> " (ConvertTo-Json $result)
$result
}
$rows = Query "select ... from ..."