Export specific columns from Excel to .csv Powershell - excel

I have such a code for exporting Excel file with two worksheets into two csv files.The problem is that I am currently exporting whole worksheets and I want to export only these 3 columns from my loop.How can I save them? They must be in order because I want to import it later to AD.
Function ExportWSToCSV ($excelFileName , $csvLoc){
#Sample use in a console: ExportWSToCSV -excelFileName "Test_Peoplesoft.xls" -csvLoc "y:\Application Data\CSVFiles\"
$CultureOld = [System.Threading.Thread]::CurrentThread.CurrentCulture
#Original culture info
$CultureUS = [System.Globalization.CultureInfo]'en-US'
#US culture info
$excelFile = "y:\Application Data\Test_Peoplesoft.xls"
#Loc of Excel file .xls , #csvLov - Loc of output files in format .csv
[System.Threading.Thread]::CurrentThread.CurrentCulture = $CultureUS
$E = New-Object -ComObject Excel.Application
$E.Visible = $false
$E.DisplayAlerts = $false
$wb = $E.Workbooks.Open($excelFile)
$intRow = 2
$intRowMax =($ws.UsedRange.Rows).count
$elements = $email -or $costcode -or $leader
Do{
foreach($ws in $wb.sheets.item("Inactive")){
if($elements -ne $null ){
$email = $ws.Cells.Item($intRow, 4).Value()
$costcode = $ws.Cells.Item($intRow, 15).Value()
$leader = $ws.Cells.Item($intRow, 20).Value()
}else{Write-Host "Null Value in one of the attributes"}
}
<#
foreach($ws in $wb.sheets.item("Inactive")){
$email = $ws.Cells.Item($intRow, 4).Value()
$costcode = $ws.Cells.Item($intRow, 15).Value()
$leader = $ws.Cells.Item($intRow, 20).Value()
}
#>
$user = $email + "_" + $costcode + "_" + $leader
write-host $intRow " " $user
$intRow++
}While ($ws.Cells.Item($intRow,1).Value() -ne $null)
foreach ($ws in $wb.Worksheets)
{
Write-Host "Processing Worksheet: " $ws.Name
$n = $csvLoc + $excelFileName + "_" + $ws.Name
#Name variable - Output file loc + excel file name + worksheet name
$ws.SaveAs($n + ".csv", 6)
#Saving file to .csv
}
$E.Quit()
[System.Threading.Thread]::CurrentThread.CurrentCulture = $CultureOld
}

Something like this should work:
First, setup an array for containing our list of exported users like so:
$exportList = #()
(place before you start looping the rows)
Then loop through the rows on your worksheet with your do while loop and add a user object to add the your export list like so
# Create userObj with the required properties
$userObj = New-Object System.Object
$userObj | Add-Member -Type NoteProperty -Name Email -Value $email
$userObj | Add-Member -Type NoteProperty -Name CostCode -Value $costcode
$userObj | Add-Member -Type NoteProperty -Name Leader -Value $leader
# Add $userObj to our list to be exported
$exportList += $userObj
And ofcourse export it to .csv
$exportList | Export-Csv $pathToCsvFile

Related

Powershell, close Excel

I have the following code and it works perfectly except it's not closing Excel properly. It's leaving an Excel process running.
Is there a way to close Excel properly without killing the process?
Since i'm using other Excel files while running this script i can not kill all active Excel processes.
I think i tried everything i found online.
$WorkDir = "D:\Test\QR_ES\RG_Temp"
$BGDir = "D:\Test\QR_ES\3_BG"
$File = "D:\Test\QR_ES\4_Adr_Excel\KD_eMail.xlsx"
$SentDir = "D:\Test\QR_ES\RG_Temp\Sent\Dunning"
chdir $WorkDir
$firstPageList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($firstPage in $firstPageList)
{
$secondPage = "$BGDir\BG_RG.pdf"
$output = "Dunn-$firstPage"
invoke-command {pdftk $firstPage background $secondPage output $output}}
del 1*.pdf
gci $WorkDir\Dunn-*.pdf | rename-item -newname {$_.Name.Substring(5)} -Force
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Workbook = $Excel.workbooks.open($file)
$DunnList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($Dunn in $DunnList)
{
$Worksheets = $Workbooks.worksheets
$Worksheet = $Workbook.Worksheets.Item("KD_eMail")
$Range = $Worksheet.Range("A1").EntireColumn
$DunnSearch = $Dunn.Substring(0,5)
$SearchString = $DunnSearch
$Search = $Range.find($SearchString)
$Recipient = $Worksheet.Cells.Item($Search.Row, $Search.Column + 1)
$Msg = "<span style='font-family:Calibri;font-size:12pt;'>Test</span>"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$namespace.Logon($null, $null, $false, $true)
$EmailFrom = ('test#test.com')
$account = $outlook.Session.Accounts.Item($EmailFrom)
$Mail = $Outlook.CreateItem(0)
$Mail.HTMLBody = $Msg
$Mail.Subject = "OP - $SearchString"
$Mail.To = $Recipient
function Invoke-SetProperty {
param(
[__ComObject] $Object,
[String] $Property,
$Value
)
[Void] $Object.GetType().InvokeMember($Property,"SetProperty",$NULL,$Object,$Value)
}
Invoke-SetProperty -Object $mail -Property "SendUsingAccount" -Value $account
$Mail.Attachments.Add("$WorkDir\$Dunn")
$Mail.Save()
$Mail.close(1)
$Mail.Send()}}
$workbook.close($false)
$Excel.Quit()
chdir $WorkDir
del 1*.pdf
See this post:
https://stackoverflow.com/a/35955339/5329137
which is not accepted as an answer, but I believe is the full, correct way to close Excel.
This is what did it for me:
$FilePID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -like 'FileName.xlsx*' }).Id
$Workbook.Save()
$Workbook.close($false)
Stop-Process $FilePID
Elaborating on #ASD's answer, since the MainWindowTitle doesn't (always) include the file suffix (.xlsx) you may have to strip that when comparing it to the filename. I'm using -replace to use a Regex match of everything before the last dot.
$excelPID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -eq $fileName -replace '\.[^.]*$', '' }).Id
$workbook.Close()
Stop-Process $excelPID

Multiple csv files into a xlsx file but different sheets using powershell

I have 20 csv files. Each are unrelated. How do I combine them together into one xlsx file with 20 sheets, each named after the csv files.
$root = "C:\Users\abc\Desktop\testcsv"
$CSVfiles = Get-ChildItem -Path $root -Filter *.csv
$xlsx = "C:\Users\abc\Desktop\testxl.xlsx" #output location
$delimiter = "," #delimiter
#Create a excel
$xl=New-Object -ComObject Excel.Application
$xl.Visible=$true
#add a workbook
$wb=$xl.WorkBooks.add(1)
ForEach ($csv in $CSVfiles){
#name the worksheet
$ws=$wb.WorkSheets.item(1)
$ws.Name = [io.path]::GetFileNameWithoutExtension($csv)
$TxtConnector = ("TEXT;" + $csv)
$Connector = $ws.QueryTables.add($TxtConnector,$ws.Range("A1"))
$query = $ws.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $delimiter
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,1 * $ws.Cells.Columns.Count
$query.AdjustColumnWidth = 1
# Execute & delete the import query
$query.Refresh()
$query.Delete()
$wb.SaveAs($xlsx,51)
}
# Save & close the Workbook as XLSX.
$xl.Quit()
This way, change the first line to the folder where you store those 20 CSV files and then
$path="c:\path\to\folder" #target folder
cd $path;
$csvs = Get-ChildItem .\* -Include *.csv
$y=$csvs.Count
Write-Host "Detected the following CSV files: ($y)"
foreach ($csv in $csvs)
{
Write-Host " "$csv.Name
}
$outputfilename = $(get-date -f yyyyMMdd) + "_" + $env:USERNAME + "_combined-data.xlsx" #creates file name with date/username
Write-Host Creating: $outputfilename
$excelapp = new-object -comobject Excel.Application
$excelapp.sheetsInNewWorkbook = $csvs.Count
$xlsx = $excelapp.Workbooks.Add()
$sheet=1
foreach ($csv in $csvs)
{
$row=1
$column=1
$worksheet = $xlsx.Worksheets.Item($sheet)
$worksheet.Name = $csv.Name
$file = (Get-Content $csv)
foreach($line in $file)
{
$linecontents=$line -split ',(?!\s*\w+")'
foreach($cell in $linecontents)
{
$worksheet.Cells.Item($row,$column) = $cell
$column++
}
$column=1
$row++
}
$sheet++
}
$output = $path + "\" + $outputfilename
$xlsx.SaveAs($output)
$excelapp.quit()
cd \ #returns to drive root
https://stackoverflow.com/a/51094040/5995160 answer is too slow when dealing with csv's with a ton of data, I modified this solution to use https://github.com/dfinke/ImportExcel. This has greatly improved the performance of this task, at least for me.
Install-Module ImportExcel -scope CurrentUser
$csvs = Get-ChildItem .\* -Include *.csv
$csvCount = $csvs.Count
Write-Host "Detected the following CSV files: ($csvCount)"
foreach ($csv in $csvs) {
Write-Host " -"$csv.Name
}
$excelFileName = $(get-date -f yyyyMMdd) + "_" + $env:USERNAME + "_combined-data.xlsx"
Write-Host "Creating: $excelFileName"
foreach ($csv in $csvs) {
$csvPath = ".\" + $csv.Name
$worksheetName = $csv.Name.Replace(".csv","")
Write-Host " - Adding $worksheetName to $excelFileName"
Import-Csv -Path $csvPath | Export-Excel -Path $excelFileName -WorkSheetname $worksheetName
}
This solution assumes that the user has already changed directories to where all the csv's live.
See below for a solution with uses the OpenText method.
At least two things to note:
I'm assuming your workbook creates a single sheet by default. if creates more than that, you will need to modify the script so that these additional sheets are deleted from the end result.
The way you specify TextFileColumnDataTypes is quite clever. You will need to modify it and feed the array to the FieldInfo argument below. See the documentation linked above for the kind of array it is expecting.
$CSVfiles = Get-ChildItem -Path $root -Filter *.csv
$xlsx = "C:\Users\abc\Desktop\testxl.xlsx" #output location
#Create a excel
$xl = New-Object -ComObject Excel.Application
$xl.Visible=$true
#add a workbook
$wb = $xl.WorkBooks.add(1)
# how many worksheets do you have in your original workbook? Assuming one:
$ws = $wb.Worksheets.Item(1)
ForEach ($csv in $CSVfiles){
# OpenText method does not work well with csv files
Copy-Item -Path $csv.FullName -Destination ($csv.FullName).Replace(".csv",".txt") -Force
# Use OpenText method. FieldInfo will need to be amended to suit your needs
$xl.WorkBooks.OpenText(`
($file.FullName).Replace(".csv",".txt"), # Filename
2, # Origin
1, # StartRow
1, # DataType
1, # TextQualifier
$false, # ConsecutiveDelimiter
$false, # Tab
$false, # Semicolon
$true, # Comma
$false, # Space
$false, # Other
$false, # OtherChar
#() # FieldInfo
)
$tempBook = $xl.ActiveWorkbook
$tempBook.worksheets.Item(1).Range("A1").Select() | Out-Null
$tempBook.worksheets.Item(1).Move($wb.Worksheets.Item(1)) | Out-Null
# name the worksheet
$xl.ActiveSheet.Name = $csv.BaseName
Remove-Item -Path ($csv.FullName).Replace(".csv",".txt") -Force
}
$ws.Delete()
# Save & close the Workbook as XLSX.
$wb.SaveAs($xlsx,51)
$wb.Close()
$xl.Quit()

Passing CSV to Excel Workbook (Not From File)

I have a folder of CSV files that contain log entries. For each entry of the CSV, if the Risk property is not Low and not None then I put it in an accumulation CSV object. From there, I want to import it into an Excel Workbook directly WITHOUT having to save the CSV to file.
$CSVPaths = (Split-Path $PSCommandPath)
$AccumulateExportPath = (Split-Path $PSCommandPath)
$FileName="Accumulate"
$Acc=#()
Foreach ($csv in (Get-ChildItem C:\Scripts\Nessus\Sheets |? {$_.Extension -like ".csv" -and $_.BaseName -notlike "$FileName"}))
{
$Content = Import-CSV $csv.FullName
Foreach ($Log in $Content)
{
If ($Log.Risk -ne "None" -and $Log.Risk -ne "Low")
{
$Acc+=$Log
}
}
}
$CSV = $ACC |ConvertTo-CSV -NoTypeInformation
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$Script:Excel = New-Object -ComObject Excel.Application
$Excel.Visible=$True
#$Excel.Workbooks.OpenText($CSV) What should replace this?
Is there a Method like OpenText() that lets me pass a CSV object instead of a filepath to a CSV file or am I going to have to write my own conversion function?
Interesting question. I'm not aware of a method that allows you to pass a CSV Object.
However, if your result CSV is not too big and you are using PowerShell 5.0+ you could convert the object to a string and leverage Set-Clipboard (more info)
$headers = ($csv | Get-Member | Where-Object {$_.MemberType -eq "NoteProperty"}).Name
$delim = "`t"
# headers
foreach($header in $headers){
$myString += $header + $delim
}
# trim delimiter at the end, and add new line
$myString = $myString.TrimEnd($delim)
$myString = $myString + "`n"
# loop over each line and repeat
foreach($line in $csv){
foreach($header in $headers){
$myString += $line.$header + $delim
}
$myString = $myString.TrimEnd($delim)
$myString = $myString + "`n"
}
# copy to clipboard
Set-Clipboard $myString
# paste into excel from clipboard
$Excel.Workbooks.Worksheets.Item(1).Paste()
Here is another way to create an Excel spreadsheet from PowerShell without writing a .csv file.
$dirs = 'C:\src\t', 'C:\src\sql'
$records = $()
$records = foreach ($dir in $dirs) {
Get-ChildItem -Path $dir -File '*.txt' -Recurse |
Select-Object #{Expression={$_.FullName}; Label="filename"}
}
#open excel
$excel = New-Object -ComObject excel.application
$excel.visible = $false
#add a default workbook
$workbook = $excel.Workbooks.Add()
#remove worksheet 2 & 3
$workbook.Worksheets.Item(3).Delete()
$workbook.Worksheets.Item(2).Delete()
#give the remaining worksheet a name
$uregwksht = $workbook.Worksheets.Item(1)
$uregwksht.Name = 'File Names'
# Start on row 1
$i = 1
# the .appendix to $record refers to the column header in the csv file
foreach ($record in $records) {
$excel.cells.item($i,1) = $record.filename
$i++
}
#adjusting the column width so all data's properly visible
$usedRange = $uregwksht.UsedRange
$usedRange.EntireColumn.AutoFit() | Out-Null
#saving & closing the file
$outputpath = Join-Path -Path $Env:USERPROFILE -ChildPath "desktop\exceltest.xlsx"
$workbook.SaveAs($outputpath)
$excel.Quit()

Loop through and replace backslash in filename (string)

I am trying to replace a \ contained in the filenames I have queried from the database. My script below loops through a CSV containing project codes. For each project code the database is queried and the project name is retrieved.
However, the projectname contains \ which I am trying to replace.
$startRow = 2
$col = 3
$excel = New-Object -COM Excel.Application
$wb = $excel.Workbooks.Open("\myprojectfolder\projectcodes.csv")
$excel.Visible = $false
for ($i = 1; $i -le $wb.Sheets.Count; $i++){
$sh = $wb.Sheets.Item($i)
$endRow = $sh.UsedRange.Rows.Count
$rangeAddress = $sh.Cells.Item($startRow,$col).Address() + ":" +
$sh.Cells.Item($endRow,$col).Address()
$sh.Range($rangeAddress).Value2 | foreach {
#GET PROJECT NAME TO APPEND TO FOLDER NAME
$projectCode = $_
$Server= "MYSERVER"
$Database = "MYDATABASE"
$SQLQuery = $("SELECT [description] FROM [dbo].[projects] WHERE [project] = '$projectCode'")
$Connection = New-Object System.Data.SQLClient.SQLConnection
$Connection.ConnectionString = "server='$Server';database='$Database';trusted_connection=true;"
$Connection.Open()
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$Command.CommandText = $SQLQuery
$Reader = $Command.ExecuteReader()
while ($Reader.Read()) {
$projectName = $Reader.GetValue($1)
#CHECK AND REPLACE '\' CHARACTER IN PROJECTNAME
if ($projectName -like '*\\*') {
Write-Debug "PROJECT NAME CONTAINS \"
$projectName.Replace('\\', '_')
}
$folderPath = "\\myfolder\"
$pathTogether = $folderPath + $projectCode + "_" + $projectName + "\"
New-Item -Path $pathTogether -Type Directory -force
#CHECK IF FILE EXISTS IN APPROPRIATE DIRECTORY
$testFile = $pathTogether + $projectCode + "_" + $projectName + ".xlsm"
$fileExist = Test-Path $testFile
if ($fileExist -eq $false) {
$templateFile = $folderPath + "my_template\my_template.xlsm"
Copy-Item $templateFile $pathTogether
$newPath = $pathTogether + "\my_template.xlsm"
$saveFile = $projectCode + "_" + $projectName + ".xlsm"
$renameToOLD = $projectCode + "_" + $projectName + "_RENAMED" + ".xlsm"
#RENAME PROJECT FILE TO HAVE OLD IN FILENAME
Rename-Item $newPath $saveFile
$projectxlFile = New-Object -COM Excel.Application
$projectxlFile.workbooks.open($pathTogether + "\" + $saveFile)
$queryWS = $projectxlFile.worksheets.Item("Query")
$queryWS.Cells.Item(8,2) = $projectCode
$projectxlFile.DisplayAlerts = $False
$projectxlFile.Visible = $False
$savePath = $pathTogether + $saveFile
#Add-Type -AssemblyName Microsoft.Office.Interop.Excel
#$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbookMacroEnabled
$projectxlFile.ActiveWorkbook.Save()
$projectxlFile.Workbooks.Close()
$projectxlFile.Quit()
$ProcID = Get-Process |
Where-Object {$_.MainWindowHandle -eq $projectxlFile.HWND} |
Select -ExpandProperty ID
Get-Process -Id $ProcID | Stop-Process -Force
##[System.Runtime.Interopservices.Marshal]::ReleaseComObject($projectxlFile)
}
}
$Connection.Close()
}
}
$excel.Workbooks.Close()
$excel.Quit()
$ProcID2 = Get-Process |
Where-Object {$_.MainWindowHandle -eq $excel.HWND} |
Select -ExpandProperty ID
Get-Process -Id $ProcID2 | Stop-Process -Force
###[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
$projectName.Replace('\\', '_')
By default the Replace() method does regular string replacements, so the above would only replace double backslashes with an underscore. Also, it doesn't replace in-place, so you need to assign the modified string back to the variable:
$projectName = $projectName.Replace('\', '_')
The -like operator does wildcard matches, so you mustn't escape the backslash in that expression either, otherwise you don't even get to the replacement operation:
if ($projectName -like '*\*') {
Write-Debug 'PROJECT NAME CONTAINS \'
$projectName = $projectName.Replace('\', '_')
}

Importing large csv file into Excel using PowerShell

I'm writing a script which imports a large csv file in Excel document.
I try to use a faster way to enter the data and pass the array directly to Excel without looping it.
$p = Import-Csv -Path "C:\Report.csv" -Delimiter "`t"
$Excel01 = New-Object -ComObject Excel.Application
$Excel01.Visible = $True
$Workbook01 = $Excel01.Workbooks.Add()
$Worksheet01 = $Workbook01.Sheets.Item(1)
$Worksheet01.Activate()
$Worksheet01.Range("A1:D1").EntireColumn.Value() = $p | select field1,field2...
But when I run this it hungs...How can I do that?
OpenText() already exists in Excel. Note, however, that you MUST change the extension of the text file to something other than .csv, because Excel has its own mind about how files with that particular extension should be handled.
New-Variable -Option Constant -Name xlDelimited -Value 1
New-Variable -Option Constant -Name xlTextQualifierNone -Value -4142
New-Variable -Option Constant -Name xlWorkbookDefault -Value 51
$csv = 'C:\path\to\your.csv'
$txt = $csv -replace '\.csv$','.txt'
$xls = $csv -replace '\.csv$','.xlsx'
Rename-Item $csv $txt
$xl = New-Object -COM 'Excel.Application'
$xl.Workbooks.OpenText($txt, [Type]::Missing, [Type]::Missing, $xlDelimited, $xlTextQualifierNone, $false, $true)
$wb = $xl.Workbooks | ? { $_.FullName -eq $txt }
$wb.SaveAs($xls, $xlWorkbookDefault)
$wb.Close()
$xl.Quit()
The [Type]::Missing values are required for parameters that should retain their default value.
Quick and dirty. Maybe you can optimize it :-)
$p = Import-Csv -Path "C:\Report.csv" -Delimiter "`t"
$Excel01 = New-Object -ComObject Excel.Application
$Excel01.Visible = $True
$Workbook01 = $Excel01.Workbooks.Add()
$Worksheet01 = $Workbook01.Sheets.Item(1)
$Worksheet01.Activate()
#Add csv header to excel
For ($i = 0; $i -lt ($p | Get-Member | Where-Object -FilterScript {$_.MemberType -eq "NoteProperty"}).Count; $i ++) {
$Worksheet01.Cells.Item(1,(1+$i)) = "$(($p | Get-Member | Where-Object -FilterScript {$_.MemberType -eq "NoteProperty"})[$i].Name)"
}
#Add csv data to ecxel
$startRow = 2
For ($i = 0; $i -lt ($p | Measure-Object).Count; $i ++) {
For ($i2 = 0; $i2 -lt ($p[$i] | Get-Member | Where-Object -FilterScript {$_.MemberType -eq "NoteProperty"}).Count; $i2 ++) {
$PropertyName = ($p[$i2] | Get-Member | Where-Object -FilterScript {$_.MemberType -eq "NoteProperty"})[$i2].Name
$Worksheet01.Cells.Item($startRow,(1+$i2)) = "$($p[$i].$PropertyName)"
}
$startRow ++
}

Resources