I have a list of files that I am breaking up into parts. That works, but I want to be able to reference each part individually. The issue is with my array not wanting/being able to take in a string. The file naming format is custID_invID_prodID or custID_invID_prodID_Boolvalue.
$files = Get-ChildItem test *.txt
[string]$custId = $files.Count
[string]$invID = $files.Count
[string]$prodID = $files.Count
[int]$Boolvalue = $files.Count
foreach($file in (Get-ChildItem test *.txt)) {
for($i = 0; $i -le $files.Count; $i++){
$custId[$i], $invID[$i], $prodID[$i], $Boolvalue[$i] = $file.BaseName -split "_"
Write-Host $custId, $invID, $prodID, $Boolvalue
}
}
The error message I am seeing is:
Unable to index into an object of type System.String.
How can I do this?
I'd suggest working with objects instead of a lot of string arrays. I have an example below in which I have replaced the file listing, since I don't have the file structure in place, with an ordinary array. Just remove that array declaration and put in your Get-ChildItem call and it should work just fine.
function ConvertTo-MyTypeOfItem
{
PARAM (
[ValidatePattern("([^_]+_){3}[^_]+")]
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$StringToParse
)
PROCESS {
$custId, $invId, $prodId, [int]$value = $StringToParse -split "_"
$myObject = New-Object PSObject -Property #{
CustomerID = $custId;
InvoiceID = $invId;
ProductID = $prodId;
Value = $value
}
Write-Output $myObject
}
}
# In the test scenario I have replaced getting the list of files
# with an array of names. Just uncomment the first and second lines
# following this comment and remove the other $baseNames setter, to
# get the $baseNames from the file listing
#$files = Get-ChildItem test *.txt
#$baseNames = $files.BaseName
$baseNames = #(
"cust1_inv1_prod1_1";
"cust2_inv2_prod2_2";
"cust3_inv3_prod3_3";
"cust4_inv4_prod4_4";
)
$myObjectArray = $baseNames | ConvertTo-MyTypeOfItem
$myObjectArray
The above function will return objects with the CustomerID, InvoiceID, ProductID and Value properties. In the sample above, the function is called and the returned array value is set to the $myObjectArray/code> variable. When output in the console it will give the following output:
InvoiceID CustomerID ProductID Value
--------- ---------- --------- -----
inv1 cust1 prod1 1
inv2 cust2 prod2 2
inv3 cust3 prod3 3
inv4 cust4 prod4 4
Seems to me that you're doing it the hard way. Why 4 arrays for every "field" of a file? It's better to create array of arrays - first index would indicate a file, and second a field in file:
$files = Get-ChildItem test *.txt
$arrFiles = #(,#());
foreach($file in $files ) {
$arrFile = $file.BaseName -split "_"
$arrFiles += ,$arrFile;
}
Write-Host "listing all parts from file 1:"
foreach ($part in $arrFiles[1]) {
Write-Host $part
}
Write-Host "listing part 0 from all files":
for ($i=0; $i -lt $arrFiles.Count ; $i++) {
Write-Host $arrFiles[$i][0];
}
Because you're trying to index into an object of type System.String! You set all your variables as strings to start with and then try to assign to an index, which I presume would attempt to assign a string to the character position at the index you provide.
This is untested but should be in the right direction.
$custIdArr = #()
$invIDArr = #()
$prodIDArr = #()
$BoolvalueArr = #()
foreach($file in (Get-ChildItem test*.txt)) {
$split = $file.BaseName -split "_"
$custId = $split[0]; $custIdArr += $custId
$invID = $split[1]; $invIDArr += $invId
$prodID = $split[2]; $prodIDArr += $prodID
$boolValue = $split[3]; $boolValueArr += $boolValue
Write-Host $custId, $invID, $prodID, $Boolvalue
}
Create a set of empty arrays, loop through your directory, split the filename for each file, append the results of the split into the relevant array.
I'm assigning to $custId, $invID, $prodID, $Boolvalue for the sake of clarity above, you may choose to directly add to the array from the $split var i.e. $invIDArr += $split[1]
Related
questions 1.
The folder name is written in column A of Excel.
In column B of Excel, the file name to be modified for each folder is written.
There are a number of folders to run.
When the folder name in Excel column A is the same, execute the Powershell source in each folder.
Powershell source to run
$nr = 1
dir |
ForEach{ Rename-Item $_ -NewName ( 'B column data must be entered_{0}.jpg' -f $nr++) }
That is, when the cell value of Excel A1 is the same as the folder name
I am trying to change the name of a number of jpg files in the file with the cell value of Excel B1 and add a number afterward.
questions 2.
The folder name is written in column A of Excel.
In column B of Excel, I wrote down the Powershell source values to be applied to each folder.
The reason why you need to apply the Powershell source for each folder is that there are multiple folders in the folder and the names of each folder are different.
That is, when executing Powershell, the Excel column A value is compared, and when the folder name is the same, the Powershell source written in the Excel column B is applied.
In conclusion, questions 1 and 2 will give the same result.
It doesn't matter what you do.
To solve this, we try to execute a For statement or a Foreach statement.
Can you help. please
Since you want to use data from an Excel sheet, here's a function that reads a single Excel sheet column and return it as array
function Import-ExcelColumn {
# returns an array of Excel Column values
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string]$Path,
[int]$WorkSheetIndex = 1,
[int]$ColumnIndex = 1,
[switch]$SkipHeader,
[switch]$RemoveEmptyValues
)
# 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
if ($SkipHeader) { $firstRow++ }
### write-host "first: $firstRow last: $lastRow"
# collect the values in this column in variable $result
$result = for ($row = $firstRow; $row -le $lastRow; $row++) {
$worksheet.Cells.Item($row, $ColumnIndex).Value2
}
$excel.Quit()
# IMPORTANT: clean-up used Com objects
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($worksheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
if ($RemoveEmptyValues) { $result = $result | Where-Object { $_ -match '\S' }
# $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
}
Having that function in place at the top of your code (and looking at your previous questions about this), you only need to read ColumnA in which the foldernames to process are stored.
At the moment, you have another column ColumnB that holds the powershell code to do the renaming of the files inside these folders. However, since that code is the same for every folder, you don't need that at all.
The code should rename every .jpg file in the folders to become <directoryName>_<sequenceNumber>.jpg, where is a numeric value starting at 1 and incrementing as you go through the files and <directoryName> is the name of the file's parent directory.
# enter the full path to your Excel file here
$ExcelFile = 'Full\Path\To\The\Excel\File.xlsx'
# this is the folder where all subfolder mentioned in the Excel file are
$rootFolder = 'C:\Image'
# read ColumnA (1st column) from the Excel file and collect the values in an array
$folderList = Import-ExcelColumn -Path $ExcelFile -SkipHeader -RemoveEmptyValues
# loop through the array of subfolder names and process each one
foreach ($dir in $folderList) {
# combine the rootfolder and the subfolder to get a complete path
$path = Join-Path -Path $rootFolder -ChildPath $dir
# initialize the sequence number to 1
$nr = 1
# get a list of FileInfo objects of all .jpg files in that folder
(Get-ChildItem -Path $path -Filter '*.jpg' -File) |
ForEach-Object {
$_ | Rename-Item -NewName ('{0}_{1}.jpg' -f $_.Directory.Name, $nr++ )
}
}
I am currently working on a script where I use several arrays to lookup inventory lists in Excel. After running a few tests I can get my script to read the contents of each cell (thanks to some research from this site!) but I can't use the contents of the cell in a variable. I receive an "Invalid Index" error when I try to switch to a worksheet using contents from one of the cells being read. I've also added an example of how the data is arranged.
#Test array
$array = "Dog", "Cat", "Mouse", "Tiger"
#Location of the Excel file to edit
$FileLoc = "Q:\Cutsheet.xlsx"
#Create Excel Com Object, and display it
$excel = new-object -com Excel.Application
$excel.visible = $true
#Open Workbook
$workbooks = $excel.workbooks.Open($FileLoc)
$worksheets = $workbooks.Worksheets
$worksheet = $worksheets.item("DATA")
#opens inventory workbook
$source = $excel.workbooks.Open("Q:\inventory.xlsx")
$sourceSheets = $source.Worksheets
<#
#This loop will search for match element of the array with a corresponding cell on an excel spreadsheet.
#That cell is grouped with several names of inventory worksheet names. The script will copy each inventory
#and append it to the existing cutsheet.
#>
foreach($element in $array) {
for ($i = 9; $i -lt 20; $i++) {
if ($worksheet.Cells.Item($i, 1).Text -eq $element) {
$j = 2
while ($worksheet.Cells.Item($i, $j).Text -ne "") {
Write-Host $worksheet.Cells.Item($i, $j).Value2
$name = $worksheet.Cells.Item($i, $j).Value2
$sourceSheet = $sourceSheets.Item($name)
$sourceSheet.Copy([system.type]::missing, $worksheets)
$j++
}
}
}
}
Example spreadsheet
Due to restrictions I either need to use VB or PowerShell for this task.
I have an Excel that looks like:
ColumA, ColumB,ColumC,ColumD,ColumE,ColumF
000|Txt,MoreTxt , ColumB,ColumC,ColumD,ColumE,ColumF
I read about import_csv -header, but I'm under to successfully do it. I'll post my script below. The export I expect is:
ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
000, ColumB, ColumC, ColumD, ColumE, ColumF
Only Colum gets modified, and I -only- need the digits from before that pipe. It also has to stay three digits, so 1 becomes 001, etc.
This is the script I modified based on some previous inquiries I saw, and the MS Tutorial.
$file = import-csv "C:\path\to\my\file\test.csv"
foreach ($row in $file){
$tempfile = New-Object psobject -Property #{
ColumA = $row. 'ListName'.substring(0,2)
ColumB = $row. 'ColumB'
ColumC = $row. 'ColumC'
ColumE = $row. 'ColumE'
ColumF = $row. 'ColumF'
}
$expandfile = #()
$expandfile += $tempfile | select ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
}
PS gives me both errors on not liking everything I have in quotes (Which I thought was the column name, but I guess not. And also a parse error on the entire array. Essentially the entire script.
UPDATE
Providing real examples of source.
"Tiam
Name",SiamName,Siam,Ciam,Piam,Liam,Niam,Diam
"002|City, State","City, State - Some text (15092)",1,"3,408",99,"3,408",780,22.89%
"009|City, State","City, State - Some Text (E) (15450)",1,"1,894",81,"1,894",543,28.67%
Edit:
$expandfile = Import-Csv "C:\path\to\my\file\test.csv" | ForEach-Object {
$_."Tiam`r`nName" = $_."Tiam`r`nName".SubString(0,3)
$_
}
I have a fairly simple request (for me it is quite tough task tbh).
I have two CSV files which I want to convert to Excel so each of these two CSV files would occupy one sheet each.
So far, I have made it work, but I have this small thing I want to correct.
One of the cells in CSV contains multiple text lines, something like this:
This is entry 1
This is entry 2
I would like to have these two entries to be imported into Excel cell the same way it is in CSV, but when I check my Excel file, the second entry is imported into next row:
Row 1 Cell1 - This is entry 1
Row 2 Cell1 - This is entry 2
I don't know if I should work with .NET class worksheet.UsedRange.EntireRow or worksheet.UsedRange.EntireColumn or something else.
I was checking MSDN, but since I am still a noob, I couldn't find anything.
This is the sample of my code:
Function Merge-CSVFiles
{
Param(
$CSVPath = ".\Reports",
$XLOutput=".\final_final_report.xlsx"
)
$csvFiles = Get-ChildItem ("$CSVPath\*") -Include *.csv
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$Excel.sheetsInNewWorkbook = $csvFiles.Count
$workbooks = $excel.Workbooks.Add()
$CSVSheet = 1
Foreach ($CSV in $Csvfiles)
{
$worksheets = $workbooks.worksheets
$CSVFullPath = $CSV.FullName
$SheetName = ($CSV.name -split "\.")[0]
$worksheet = $worksheets.Item($CSVSheet)
$worksheet.Name = $SheetName
$TxtConnector = ("TEXT;" + $CSVFullPath)
$CellRef = $worksheet.Range("A1")
$Connector = $worksheet.QueryTables.add($TxtConnector,$CellRef)
$worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
$worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
$worksheet.QueryTables.item($Connector.name).Refresh()
$worksheet.QueryTables.item($Connector.name).delete()
$worksheet.UsedRange.EntireColumn.AutoFit()
$CSVSheet++
}
$workbooks.SaveAs($XLOutput,51)
$workbooks.Saved = $true
$workbooks.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbooks) | Out-Null
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
I am not sure why the method you are using does that but I was able to reproduce on my system as well. I do some similar operations in one of my scripts that builds a large Excel workbook and I have used the following method with success:
Import the CSV with Import-CSV
Convert it to a multi-dimensional array
Write the array to Value2 of the range object
For example, replace your code that looks like this:
$TxtConnector = ("TEXT;" + $CSVFullPath)
$CellRef = $worksheet.Range("A1")
$Connector = $worksheet.QueryTables.add($TxtConnector,$CellRef)
$worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
$worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
$worksheet.QueryTables.item($Connector.name).Refresh()
$worksheet.QueryTables.item($Connector.name).delete()
With this instead:
$CsvContents = Import-Csv $CSVFullPath
$MultiArray = (ConvertTo-MultiArray $CsvContents -Headers).Value
$StartRowNum = 1
$StartColumnNum = 1
$EndRowNum = $CsvContents.Count + 1
$EndColumnNum = ($CsvContents | Get-Member | Where-Object { $_.MemberType -eq 'NoteProperty' }).Count
$Range = $worksheet.Range($worksheet.Cells($StartRowNum, $StartColumnNum), $worksheet.Cells($EndRowNum, $EndColumnNum))
$Range.Value2 = $MultiArray
For that to work you will also need the function I use for converting an object to a multi-dimensional array (based off the one posted here but with some slight modifications):
function ConvertTo-MultiArray
{
param (
$InputObject,
[switch]$Headers = $false
)
begin
{
$Objects = #()
[ref]$Array = [ref]$null
}
process
{
$Objects += $InputObject
}
end
{
$Properties = $Objects[0].PSObject.Properties | ForEach-Object{ $_.Name }
$Array.Value = New-Object 'object[,]' ($Objects.Count + 1), $Properties.Count
$ColumnNumber = 0
if ($Headers)
{
$Properties | ForEach-Object{
$Array.Value[0, $ColumnNumber] = $_.ToString()
$ColumnNumber++
}
$RowNumber = 1
}
else
{
$RowNumber = 0
}
$Objects | ForEach-Object{
$Item = $_
$ColumnNumber = 0
$Properties | ForEach-Object{
if ($Item.($_) -eq $null)
{
$Array.Value[$RowNumber, $ColumnNumber] = ""
}
else
{
$Array.Value[$RowNumber, $ColumnNumber] = $Item.($_).ToString()
}
$ColumnNumber++
}
$RowNumber++
}
$Array
}
}
I am writing a script, to automate taking reports from various SQL databases, which is currently ran once a month manually. So far I have a working prototype that will read an SQL database and will parse the information into an Excel file, save it, and then email it to someone.
What I want to be able to do is have another Excel file called emails.xlsx. This file will have three columns: Emails(A), Server(B), Database(C). I want to be able to search the file for anything in column 2 that is the same and grab the emails from those rows and put them in to a var.
#Import Email Details
$emailPath = "C:\temp\emails.xlsx"
$sheetName = "emails"
$workBook1 = $excel.Workbooks.Open($emailPath)
$worksheet = $workBook1.sheets.Item($sheetName)
$cell = 1;
$CcCheck = $worksheet.Range.("A1").Text;
FOREACH($dbase in $worksheet.Range("C1").EntireColumn)
{
DO{
$CcCheck = $worksheet.Range("A$cell").Text;
if($CcCheck -ne " ") {
$data = $worksheet.Range("C$cell").Text;
$server = $worksheet.Range("B$cell").Text;
$Cc += ", $CcCheck";
$cell++
}
} while($foreach.MoveNext() -eq $foreach.Current)
}
Write-Host " Loaded Server, $cell Emails and DB" -ForegroundColor "Green";
Write-Host "";
Import-Csv .\emails.csv -Header emails,server,database | Foreach-Object{
$dataSource = $_.server
$dataBase = $_.database
This fixed the problem by just referencing the headers after converting the excel file to a CSV.