Creating Powershell script to loop through excel file and create folders - excel

I'm new to Powerscript and looking at writing a solution to loop through an excel file and create a folder for each row in the file.
At the moment I have the following:
$objExcel = new-object -comobject excel.application
$objExcel.Visible = $True
$ExcelFilesLocation = “D:\Users\”
$UserWorkBook = $objExcel.Workbooks.Open($ExcelFilesLocation + “ProjectCodes.xlsx”)
$UserWorksheet = $UserWorkBook.Worksheets.Item(1)
$intRow = 2
Do {
$Projectcode = $UserWorksheet.Cells.Item($intRow, 1).Value()
$pos = $userLogOnEmail.IndexOf(“#”)
$intRow++
} While ($UserWorksheet.Cells.Item($intRow,1).Value() -ne $null)
$objExcel.Quit()
$a = Release-Ref($UserWorksheet)
$a = Release-Ref($UserWorkBook)
$a = Release-Ref($objExcel)
The idea is to loop through the project code column for each row. Then create a folder that is named for the project code.

Having spent painful hours wrangling Excel COM objects with PowerShell, my advice is to give up! Office COM objects are confusing, unwieldy and slow.
Replace the technology you use to access Excel in PowerShell with something better. For example, this module: https://github.com/dfinke/ImportExcel. You can use Install-Module ImportExcel -Scope CurrentUser if you're using PS 5.
Apart from being easier to use and much faster it doesn't require Excel to be installed, making your final script more portable.
Admitedly, you don't get full Excel functionality with this module but since you seem to be doing no more than reading cells, you should be fine.
Alternatively, Save your Excel file as CSV and use Import-Csv instead.

To create a new directory, use New-Item
For example, assuming $Projectcode is a string containing a valid path:
New-Item -Path $Projectcode -Type Directory

Related

Create a series of PowerShell variables from Excel to execute in Robocopy

Using the PowerShell module importexcel I have created the variables and the job is running but with a few caveats. The data from the source is dumped into the root of the destination (which is expected based on the variables I've created - which is one of many areas I ask for help).
$dataJob01 = Import-Excel "C:\temp\test.xlsx"
ForEach ($project in $dataJob01){
$source = $($dataJob01.Source)
$destination = $($dataJob01.Destination)
robocopy $source $destination /e /copy:DATU /dcopy:DAT /log:C:\temp\log.txt
}
Is there a way to append the "Folder Name" to the destination variable? Making the $destination = \\server2\share\PROJECTS\304401
Until the issue in problem 1 is solved, problem 2 is irrelevant but ideally I want the job to process 100s of lines below the excel snippet above. Unfortunately, that is failing with the code I've written. It's as if the source and destination are being bunched together vs. being processed on each line. I thought ForEach was appropriate but I'm a noob.
When I run this against multiple lines, I receive no output and the job fails.
Hopefully I'm not going about this the wrong way and someone has some pointers for me.
Is there a way to append the "Folder Name" to the destination variable? Making the $destination = \server2\share\PROJECTS\304401
Here you can use Join-Path which works perfectly for what you need:
$destination = Join-Path $project.Destination -ChildPath $project.'Folder Name'
I've noticed you're using $dataJob01, which is the array you're using to iterate, inside your foreach loop. Try using this instead (I changed the name of the variables, so it helps you understand. $line is exactly that).
$xlsx = Import-Excel "C:\temp\test.xlsx"
ForEach ($line in $xlsx)
{
$source = $line.Source
$destination = Join-Path $line.Destination -ChildPath $line.'Folder Name'
robocopy $source $destination /e /copy:DATU /dcopy:DAT /log:C:\temp\log.txt
}

import xlsx converted to csv to powershell

I need to import csv file to my PS script. In the same script I have a function to convert an xlsx file to csv, which I need to import. But I can't find a way to do that. I tried three approaches:
$CSVLicence = Import-Csv (ConvertXLSX -File $Licence) -Delimiter "," -Encoding UTF8
Where ConvertXLSX is the function to convert xlsx to csv, $Licence is a variable defined at the beginning of the script. The second approach is this:
$CSVfile = ConvertXLSX -File $Licence
$CSVLicence = ImportCsv $CSVfile -Delimiter "," -Encoding UTF8
In both cases I get an error message on the column right after "Import-Csv", that argument Path is null or empty.
The third approach was defining the path literally, which is not really a good solution, but should work, if executor of the file runs it in correct folder and uses correct file name. In this case I get an error "The member "Proemail Exchg Business" is already present" in front of the "Import-Csv" cmdlet
The csv file gets created and it looks precisely as it should, so there's obviously not an error in the converting function.
The ConvertXLSX function is defined like this:
$Licence = "licence"
Function ConvertXLSX ($File)
{
$PWD = "r:\Licence\"
$ExcelFile = $PWD + $File + ".xlsx"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$wb = $Excel.Workbooks.Open($ExcelFile)
foreach ($ws in $wb.Worksheets | where {$_.name -eq "Pridani"})
{
$ws.SaveAs($PWD + $File + ".csv",6)
}
$Excel.Quit()
}
ConvertXLSX -File $Licence
The function creates the csv file successfully, without any problems
You're currently using the wrong cmdlet. Import-Csv is designed to be used with a file.csv, not a string object or otherwise. There is a cmdlet that exists for that purpose, ConvertFrom-Csv, that takes an -InputObject parameter (indicating it takes pipeline input).
PS C:\> Get-Help -Name 'ConvertFrom-Csv'
SYNTAX
ConvertFrom-Csv [-InputObject] <PSObject[]> [[-Delimiter] <Char>] [-Header <String[]>]
Used in your example:
## Declaration of the delimiter is unnecessary when it's a comma
$CSVLicense = ConvertXLSX -File $Licence | ConvertFrom-Csv
TheIncorrigible1 explained the issue quite well. Now that you've edited the question to add the ConvertXLSX function, we can build on that to get a solution that fits.
$ws.SaveAs($PWD + $File + ".csv",6)
This part is the save to the filename $PWD + $File + ".csv". The number 6 represents the CSV XlFileFormat Enumeration.
The function does not return anything, just performs an action on external file. That's why you are getting the null or empty error. We can get it to return the file path, or to return the data in an object and use ConvertFrom-Csv.
Given your code, the object would require more work as you already have the file path there.
To return file path, change:
}
$Excel.Quit()
}
To:
}
$Excel.Quit()
return $($PWD + $File + ".csv")
}
You code should then work.
You could improve it's readability and conciseness more:
Assign the CSV filename to a variable.
Avoid assigning to $PWD as it's an alias for Get-Location
Get rid of the foreach. You are only getting one sheet and saving that sheet at the moment. If you are doing for multiple sheets, you will have to thing how to pass the multiple paths/object to Import-Csv/ConvertFrom-Csv
Putting it all together:
$Licence = "licence"
Function ConvertXLSX ($File)
{
$rootDir = "r:\Licence\"
$ExcelFile = $rootDir + $File + ".xlsx"
$CSVFile = $ExcelFile.Replace(".xlsx",".csv")
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$wb = $Excel.Workbooks.Open($ExcelFile)
$ws = $wb.Worksheets | where {$_.name -eq "Pridani"})
$ws.SaveAs($CSVFile,6)
$Excel.Quit()
return CSVFile
}
ConvertXLSX -File $Licence
I get an error "The member "Proemail Exchg Business" is already present" in front of the "Import-Csv" cmdlet
This one is handled in this StackOverflow post. Short version is you have two columns with "Proemail Exchg Business" in the first row.
PowerShell creates an object from the CSV: the column headers must be unique. Either fix in the excel sheet, or manually specify unique headers using -Header as in linked post.

How to search an entire excel workbook for a particular string using powershell

I need to search for a particular string in a excel spreadsheet that has multiple sheets in it. I am looking for a way to search the entire contents of the excel file similar to the Find All option in Excel with the scope set to the workbook and not just the worksheet.
It would be really nice if there was something similar to the search string for a particular string in regular file, (ie)
gci xcelfile.xls | select-string -pattern $mySearchString
I have searched the internet and I don't see much existing information for searching the contents of an existing excel file using powershell. I am hoping I can get some pointers here to get me to my goal.
Any assistance is much appreciated.
Thanks
Don
Opens Excel
Loads File
Loops through each worksheet
Searches a range
Loops through find next Outputs Index $Column$Row
Exits Excel
$File = "C:\TEST.xlsx"
$SearchString = "TEST"
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open($File)
for($i = 1; $i -lt $($Workbook.Sheets.Count() + 1); $i++){
$Range = $Workbook.Sheets.Item($i).Range("A:Z")
$Target = $Range.Find($SearchString)
$First = $Target
Do
{
Write-Host "$i $($Target.AddressLocal())"
$Target = $Range.FindNext($Target)
}
While ($Target -ne $NULL -and $Target.AddressLocal() -ne $First.AddressLocal())
}
$Excel.Quit()

Finding content of Excel file in Powershell

I am currently working on a fairly large powershell script. However, I got stuck at one part. The issue is the following.
I have various reports with the same file name, they just have a different time stamp at the end. Within the report, I have a field displaying the date from when to when the report is from.
---> 2/1/2015 5:00:00AM to 3/1/2015 5:00:00AM <--- This is what it looks like.
This field is randomly placed on the Excel Sheet. Pretty much in the range of A5 to Z16. What I would like the script to do is:
Read the file / Check the range of cells for the dates, if the date is found and it matches my search criteria, close the sheet and move it to a different folder / If date does not match, close and check next XLS file
This is what I got so far:
$File = "C:\test.XLS"
$SheetName = "Sheet1"
# Setup Excel, open $File and set the the first worksheet
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $true
$Workbook = $Excel.workbooks.open($file)
$Worksheets = $Workbooks.worksheets
$WorkSheet = $WorkBook.sheets.item($SheetName)
$SearchString = "AM" #just for test purposes since it is in every report
$Range = $Worksheet.Range("A1:Z1").EntireColumn
$Search = $Range.find($SearchString)
If you want it to search the entire column for A to Z you would specify the range:
$Range = $Worksheet.Range("A:Z")
Then you should be able to execute a $Range.Find($SearchText) and if the text is found it will spit back the first cell it finds it in, otherwise it returns nothing. So start Excel like you did, then do a ForEach loop, and inside that open a workbook, search for your text, if it is found close it, move it, stop the loop. If it is not found close the workbook, and move to the next file. The following worked just fine for me:
$Destination = 'C:\Temp\Backup'
$SearchText = '3/23/2015 10:12:19 AM'
$Excel = New-Object -ComObject Excel.Application
$Files = Get-ChildItem "$env:USERPROFILE\Documents\*.xlsx" | Select -Expand FullName
$counter = 1
ForEach($File in $Files){
Write-Progress -Activity "Checking: $file" -Status "File $counter of $($files.count)" -PercentComplete ($counter*100/$files.count)
$Workbook = $Excel.Workbooks.Open($File)
If($Workbook.Sheets.Item(1).Range("A:Z").Find($SearchText)){
$Workbook.Close($false)
Move-Item -Path $File -Destination $Destination
"Moved $file to $destination"
break
}
$workbook.close($false)
$counter++
}
I even got ambitious enough to add a progress bar in there so you can see how many files it has to potentially look at, how many it's done, and what file it's looking at right then.
Now this does all assume that you know exactly what the string is going to be (at least a partial) in that cell. If you're wrong, then it doesn't work. Checking for ambiguous things takes much longer, since you can't use Excel's matching function and have to have PowerShell check each cell in the range one at a time.

Convert multiple xls to csv using powershell

I'm trying to convert multiple excel files (xls) to csv which is located in a folder using powershell.
I can convert a single file but need help converting multiple files in a folder.
But need advise on how to convert multiple files.
$ExcelWB = new-object -comobject excel.application
$Workbook = $ExcelWB.Workbooks.Open(c:\temp\temp.xls)
$Workbook.SaveAs("c:\temp\temp.csv",6)
$Workbook.Close($false)
$ExcelWB.quit()
You can just wrap it in a loop that iterates over all the files and change the xls extension to csv:
foreach($file in (Get-ChildItem "C:\temp")) {
$newname = $file.FullName -replace '\.xls$', '.csv'
$ExcelWB = new-object -comobject excel.application
$Workbook = $ExcelWB.Workbooks.Open($file.FullName)
$Workbook.SaveAs($newname,6)
$Workbook.Close($false)
$ExcelWB.quit()
}
There are caveats with this untested code but it should help wrap your head around your issue
$ExcelWB = new-object -comobject excel.application
Get-ChildItem -Path c:\folder -Filter "*.xls" | ForEach-Object{
$Workbook = $ExcelWB.Workbooks.Open($_.Fullname)
$newName = ($_.Fullname).Replace($_.Extension,".csv")
$Workbook.SaveAs($newName,6)
$Workbook.Close($false)
}
$ExcelWB.quit()
Take the lines in between the first and last and build a loop. Use Get-ChildItem to grab your xls files and then build a new name by replacing the extension if the FullName of the file
The conversion from xlsx files to csv can be done far quicker and without COM Objects - so without Excel installed - using the ImportExcel module developped by Doug Finke:
Install-Module -Name ImportExcel -RequiredVersion 5.4.2
gci *.xlsx | %{Import-Excel $_ | Export-Csv ($_.basename + ".csv")}
Or the other way around:
gci *.csv | %{Import-Csv $_ | Export-Excel ($_.basename + ".xlsx")}
Parameters available for the Import-Excel cmdlet:
WorksheetName
Specifies the name of the worksheet in the Excel workbook to import. By default, if no name is provided, the first worksheet will be imported.
DataOnly
Import only rows and columns that contain data, empty rows and empty columns are not imported.
HeaderName
Specifies custom property names to use, instead of the values defined in the column headers of the TopRow.
NoHeader
Automatically generate property names (P1, P2, P3, ..) instead of the ones defined in the column headers of the TopRow.
StartRow
The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row.
EndRow
By default all rows up to the last cell in the sheet will be imported. If specified, import stops at this row.
StartColumn
The number of the first column to read data from (1 by default).
EndColumn
By default the import reads up to the last populated column, -EndColumn tells the import to stop at an earlier number.
Password
Accepts a string that will be used to open a password protected Excel file.
Expanding on the answer from #arco444, if you are doing this in bulk you should create the excel object outside the loop for a much more performant conversion
$ExcelWB = new-object -comobject excel.application
foreach($file in (Get-ChildItem "C:\temp")) {
$newname = $file.FullName -replace '\.xls$', '.csv'
$Workbook = $ExcelWB.Workbooks.Open($file.FullName)
$Workbook.SaveAs($newname,6)
$Workbook.Close($false)
}
$ExcelWB.quit()
Apologies I can't comment and edit queue has been full for some time, so posting as an answer instead.

Resources