I have an excel file of new-hires for the company.
Firstly I need to hide all the columns that will be used for searching users.
That was pretty simple and I managed to do it. Now I'm left only with the columns I really need.
Now is the real problem:
I need to filter the data and then import those usernames to the PowerShell array.
So in excel it looks like this:
Then I have the function:
Function GetUsernames ($WorkSheet) {
$userName = $WorkSheet.UsedRange.Rows.Columns["UserNameColumn"].Value2
return $userName
}
But it's returning all of the records in the Username column - 651 records instead of 476.
The function is waiting for my input after I format the excel file manually.
Any directions will be appreciated! :)
What you seek is all the values from rows in a certain column that are not hidden in the Excel file.
To get those, you need to go through the Rows of the selected column.
In my Office version 2016, I cannot reference a column directly by its name, so I have extended your function to first find the column index.
Also, I have renamed the function a bit to follow the Verb-Noun convention in PowerShell
function Get-Usernames ($WorkSheet, $Column) {
# for me (office 2016) I cannot reference a Column by its name
# using Columns["UserNameColumn"], so I have to find the index first
$index = 0
if ($Column -is [int]) {
$index = $Column
}
else {
for ($col = 1; $col -le $WorkSheet.UsedRange.Columns.Count; $col++) {
$name = $WorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
if ($name -eq $Column) {
$index = $col
break
}
}
}
if ($index -gt 0) {
# now return the values in the columns for the rows that are not hidden
# skip the first row, because that is the column name itself
($WorkSheet.UsedRange.Rows.Columns($index).Rows | Select-Object -Skip 1 | Where-Object { !$_.hidden }).Value2
}
}
You can now use the function in your script like this:
$userNames = Get-Usernames $workbook.Worksheets(1) "UserNameColumn"
Related
I have only one excel file, the file has multiple spreadsheets, I loop through all the spreadsheets and find the rows whose column1 and column2 satisfy my criteria, and if it does, copy the rows to a new excel workbook, I need to copy the first row that specifies what the column names are as well, but right now I'm ignoring this to simplify the problem.
I looked up online, and found a post similar to my question, I modified the code according to my situation, here's the code:
$WindowsFolder = "C:\Users\wanlingjiang\Downloads\xlsx\"
# Create a new datatable to copy data into.
$dtExcel = New-Object System.Data.DataTable
# Counter used to only create data columns on the first index in the loop.
$count = 1
# Get all spreadsheet objects from the current folder.
$SpreadSheets = Get-ChildItem $WindowsFolder -File -Verbose
# Loop through each of the spreadsheet objects returned.
foreach ($SpreadSheet in $SpreadSheets) {
# Index the counter.
try{
# Import the data from the source spreadsheet into datatables.
$dts = Get-TablesFromXLSXWorkbook -InputFileName $SpreadSheet.FullName -Verbose -ErrorAction Stop
# We only need to work with the first datatable imported from each spreadsheet.
$Rows = $dts[0].Rows
# Create the data columns within the target datatable
# using the column headers from the current spreadsheet.
if($count -eq 1) {
foreach ($item in $dts.Columns) {
$dtExcel.Columns.Add($item.ColumnName) | Out-Null
}
$count++
}
# Loop through each row of data returned from the current spreadsheet.
foreach ($row in $Rows) {
# Determine if the 'Column1' column in the current row equals 'Criteria1' and if the 'Column2' column in the current row starts with 'Criteria2'.
# If yes, copy the data row to the target datatable.
if($row.'Column1' -eq 'Criteria1' -and $row.'Column2' -like 'Criteria2*') {
$dr = $dtExcel.NewRow()
$dr.ItemArray = $row.ItemArray.Clone()
$dtExcel.Rows.Add($dr)
}
}
} catch {
Write-Warning -Message "Something happened. Write a good error message."
}
}
# Export the target datatable to a new Excel spreadsheet.
New-XLSXWorkbook -InputTables $dtExcel -OutputFileName 'C:\Users\wanlingjiang\Downloads\xlsx\Output.xlsx' -Open
It is not working, the error message says:
New-XLSXWorkbook : The term 'New-XLSXWorkbook' is not recognized as the name of a cmdlet, function, script file, or operable program.
I removed the last line, and tried to debug, it stopped at this line:
$dts = Get-TablesFromXLSXWorkbook -InputFileName $SpreadSheet.FullName -Verbose -ErrorAction Stop
Do I need to install something? Please help. Thank you!
I have n application made in Powershell that reads result from a database and spool the data into an Excel table. When the numbers are integer i don't have any problem, but when I'm trying to spool a decimal the next error appears: "The number in this cell is formatted as text or preceded by an apostrophe".
I've tried the next after the data insert:
$range1 = "F2:G20"
$range2 = "K2:L20"
$WorkSheet.Columns.item('F').NumberFormat = "#.##0,00"
$WorkSheet.Columns.item('G').NumberFormat = "#.##0,00"
$range = $WorkSheet.Range($range1).Copy()
$x = $WorkSheet.Range($range2).Select()
$WorkSheet.UsedRange.PasteSpecial(-4163,-4142)
But no success... There is another way? I want those 2 columns F and G to be formated as Number....
As well I've tried as well :
$WorkSheet.Columns.item('F').NumberFormat = "#.##0,00"
$WorkSheet.Columns.item('G').NumberFormat = "#.##0,00"
$Tr = $WorkSheet.Range('K2','L20')
$WorkSheet.Range('F2','G20').Copy()
$Tr.Select()
$Tr.PasteSpecial(-4163)
The values are still pasting as text and not as a decimal number...
Very important : I'm trying to paste the values in a Excel Table.
OK, after too much time i found a solution, better call it work arround:
In your Excel workbook create a table with the same name of the column of you SQL table, then insert some dummy values manually.
Get your data information from the DB in this way:
$dt1 = SQLCommand -File_Path $QueryReplacedFullPath -Server "tnsdw" -Name "TMS_SalesDW" -Out
$dt_table1 = $dt1.Tables[0]
$WorkSheet = $Workbook.worksheets | where-object {$_.Name -eq $SheetName}
$WorkSheet.Activate()
Delete the dummy data inserted manually before wit the next code:
$StartRow = 2
$FinalRow = 200
$null = $WorkSheet.Range("A$($StartRow):A$($FinalRow)")
$WorkSheet.usedrange.offset($StartRow,0).specialcells(12).Entirerow.Delete()
Insert the data in your Excel table using this command:
for ([Int]$m = 0; $m -lt $dt_table1.Rows.Count; $m++)
{
for ([Int]$r = 0; $r -lt $dt_table1.Columns.Count; $r++)
{
$incolumn = $r + 1;
$inrow = $inheaderlenght + 2 + $m;
if($incolumn -gt 4)
{
$Workbook.ActiveSheet.Cells.Item($inrow, $incolumn) = [System.Convert]::ToDecimal($dt_table1.Rows[$m].ItemArray[$r])
}
else
{
$Workbook.ActiveSheet.Cells.Item($inrow, $incolumn) = $dt_table1.Rows[$m].ItemArray[$r].ToString()
}
}
}
Note that i convert the values to decimal only in the columns that i want to be decimal.
I am trying to write a script in powershell that can loop through Excel sheets from a prepared Excel file and extract a range of values in each sheet, which I then pipe into the import-csv cmdlet. This is the first step in a larger script that acts on the csv files; I am trying to consolidate all the steps into 1 convenient script.
My problem is that I need:
the script to work without Excel installed (rules out COM object Excel.Application)
cannot install powershell modules (rules out the popular ImportExcel).
is usable on xlsx files (rules out jet 4.0 with excel object 8.0)
Doesn't require downloads/admin permissions to directories or has simple workarounds for this.
In short:
Is importing an excel sheet to CSV via a PowerShell script possible with only pre-installed Windows functionalities?
The next best thing would be minimal adjustments, such as bundling a small library with the script that can be easily referenced in the script (would Open XML SDK or EPPlus 4.5.3.3 fall into this category?).
Thank you.
It's possible to work with the raw Excel data, but you will have to "reverse engineer" the format. I was able to get some useful data from a very simple sheet.
To test and play around with this create an empty folder and save an Excel document as Book1xlsx with some values like this:
| Name | Value |
| adf | 5 |
| fgfdg | 4 |
| dfgdsfg | 3 |
Then place this script there, and see the result. If your data is any more advanced with this, you probably have to spend quite a bit of time figuring out how different types and sheets are named, and how to look them up.
unzip Book1.xlsx
[xml]$sheet = Get-Content "xl\worksheets\sheet1.xml"
[xml]$strings = Get-Content "xl\sharedStrings.xml"
$stringsTable = $strings.sst.si.t
$data = $sheet.worksheet.sheetData.row | % {
# Each column for each row is in the "c" variable
# (The ,#() is a hack to avoid powershell from turning everything into a single array)
return ,#($_.c | % {
# There is a "t" property that represents the type.
if ($_.t -like "s") {
# "s" means a string. To get the actual content we need to look up in the strings xml
return $stringsTable[$_.v]
} elseif ($_.t -like "") {
# Empty type means integer, we can return the value as is
return $_.v
}
})
}
# Data will be a 2 dimensional array
# $data[0][0] will refer to A1
# $data[1][0] will refer to A2
# $data[0][1] will refer to B1
$data
Hopefully this will be enough to get you started.
Edit:
Here is also some code to convert the 2 dimensional array into a PSObject you can use with Export-Csv.
$headers = $data[0]
$dataObject = $data | Select-Object -Skip 1 | % {
$row = $_
$index = 0
$object = #{}
foreach ($column in $row) {
$object[$headers[$index]] += $column
$index++
}
return [PSCustomObject]$object
}
$dataObject | Export-Csv ...
I'm working on a PS script to take a row of data from an Excel spreadsheet and populate that data in certain places in a Word document. To elaborate, we have a contract tracking MASTER worksheet that among other things contains data such as name of firm, address, services, contact name. Additionally, we have another TASK worksheet in the same workbook that tracks information such as project owner, project name, contract number, task agree number.
I'm writing a script that does the following:
Ask the user through a message box what kind of contract is being written ("Master", or "Task")
Opens the workbook with the appropriate worksheet opened ("Master" tab or "Task" tab)
Asks the user through a VB InputBox from which Excel row of data they want to use to populate the Word contract
Extracts that row of data from Excel
Outputs certain portions of that row of data to certain location in a Word document
Saves the Word document
Opens the Word document so the user can continue editing it
My question is this - using something like PSExcel, how do I extract that row of data out to variables that can be placed in a Word document. For reference, in case you're going to reply with a snippet of code, here are what the variables are defined as for the Excel portion my script:
$Filepath = "C:\temp\ContractScript\Subconsultant Information Spreadsheet.xlsx"
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open($Filepath)
$Worksheet = $Workbook.sheets.item($AgreementType)
$Excel.Visible = $true
#Choosing which row of data
[int]$RowNumber = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the row of data from $AgreementType worksheet you wish to use", "Row")
Additionally, the first row of data in the excel worksheets are the column headings, in case it matters.
I've gotten this far so far:
import-module psexcel
$Consultant = new-object System.Collections.Arraylist
foreach ($data in (Import-XLSX -path $Filepath -Sheet $AgreementType -RowStart $RowNumber))
{
$Consultant.add($data)'
But I'm currently stuck because I can't figure out how to reference the data being added to $consultant.$data. Somehow I need to read in the column headings first so the $data variable can be defined in some way, so when I add the variable $consultant.Address in Word it finds it. Right now I think the variable name is going to end up "$Consultant.1402 S Broadway" which obviously won't work.
Thanks for any help. I'm fairly new to powershell scripting, so anything is much appreciated.
I have the same issue and searching online for solutions in a royal PITA.
I'd love to find a simple way to loop through all of the rows like you're doing.
$myData = Import-XLSX -Path "path to the file"
foreach ($row in $myData.Rows)
{
$row.ColumnName
}
But sadly something logical like that doesn't seem to work. I see examples online that use ForEach-Object and Where-Object which is cumbersome. So any good answers to the OP's question would be helpful for me too.
UPDATE:
Matthew, thanks for coming back and updating the OP with the solution you found. I appreciate it! That will help in the future.
For my current project, I went about this a different way since I ran into lack of good examples for Import-XLSX. It's just quick code to do a local task when needed, so it's not in a production environment. I changed var names, etc. to show an example:
$myDataField1 = New-Object Collections.Generic.List[String]
$myDataField2 = New-Object Collections.Generic.List[String]
# ...
$myDataField10 = New-Object Collections.Generic.List[String]
# PSExcel, the third party library, might want to install it first
Import-Module PSExcel
# Get spreadsheet, workbook, then sheet
try
{
$mySpreadsheet = New-Excel -Path "path to my spreadsheet file"
$myWorkbook = $mySpreadsheet | Get-Workbook
$myWorksheet = $myWorkbook | Get-Worksheet -Name Sheet1
}
catch { #whatever error handling code you want }
# calculate total number of records
$recordCount = $myWorksheet.Dimension.Rows
$itemCount = $recordCount - 1
# specify column positions
$r, $my1stColumn = 1, 1
$r, $my2ndColumn = 1, 2
# ...
$r, $my10thColumn = 1, 10
if ($recordCount -gt 1)
{
# loop through all rows and get data for each cell's value according to column
for ($i = 1; $i -le $recordCount - 1; $i++)
{
$myDataField1.Add($myWorksheet.Cells.Item($r + $i, $my1stColumn).text)
$myDataField2.Add($myWorksheet.Cells.Item($r + $i, $my2ndColumn).text)
# ...
$myDataField10.Add($myWorksheet.Cells.Item($r + $i, $my10thColumn).text)
}
}
#loop through all imported cell values
for ([int]$i = 0; $i -lt $itemCount; $i++)
{
# use the data
$myDataField1[$i]
$myDataField2[$i]
# ...
$myDataField10[$i]
}
Here is my sample code:
$wb = $excel.ActiveWorkbook
ForEach ($ws in $wb.Worksheets) {
"Working on {0}" -f $ws.Name
forEach ($row in $ws.Rows) {
forEach($cell in $row.Cells) {
if ($cell.HasFormula) {
$formula = $cell.Formula
if ( ($formula -match "foobar") {
"{0} R{1}C{2}:{3}" -f $ws.Name, $row.Row, $cell.Column, $formula
}
}
}
}
}
The performance is not good because the spreadsheet is too big. I want to
1) can I retrieve all formula in one function call? Does excel support it?
2) In the case a formula is assigned to an array of cells, how can I extract the formula?
Maybe this link can give you some hints to get a list of formulas in a sheet.
$excel = New-Object -com Excel.Application
$excel.Workbooks.Open( "C:\cartel1.xls" )
$sheet = $excel.Sheets.item(1) # select the sheet to looking for formulas
$cellsWithFormula = $sheet.Cells.SpecialCells( -4123 , 23) # (XlCellType constant, XlSpecialCellsValue constant) - Return a Range of cell with formula
$cellsWithFormula | select row, column, formula, formulaArray | ft -autosize # get a list of cell and formula and formulaArray
note: type xlCell Constants