Error append line to excel file with powershell - excel

I am getting error-messages, if i am running this code. I want to read users active directory data such as username, name, mail and append these informations to an xlsx file for license-tracking and ordering.
$user = Read-Host "Username"
$data = Get-ADUser -Identity $user -Properties * | select SamAccountName,AdminDisplayName,EmailAddress
$user = $data.SamAccountName
$name = $data.AdminDisplayName
$mail = $data.EmailAddress
$clo = Read-Host "Alter Computername (Nur bei Übertrag)"
$cln = Read-Host "Neuer Computername"
if ($clo -ne '') {
$out = "Übertrag: $($user), $($name), $($mail), Alter PC: $($clo), neuer PC: $($cln)"
}else {
$kst = Read-Host "Kostenstelle"
$out = "$($user), $($name), $($mail), PC: $($cln), KST: $kst"}
$excel_file_path = '"##PATHTOFILE#CENSORED#\test.xlsx"'
## Instantiate the COM object
$Excel = New-Object -ComObject Excel.Application
$ExcelWorkBook = $Excel.Workbooks.Open($excel_file_path)
$ExcelWorkSheet = $Excel.WorkSheets.item("Tabelle1")
$ExcelWorkSheet.activate()
## Find the first row where the first 7 columns are empty
$row = ($ExcelWorkSheet.UsedRange.Rows | ? { ($_.Value2 | ? {$_ -eq $null}).Count -eq 7 } | select -first 1).Row
$ExcelWorkSheet.Cells.Item($row,1) = 'COLUMN 1 Text'
$ExcelWorkSheet.Cells.Item($row,2) = 'COLUMN 2 Text'
$ExcelWorkSheet.Cells.Item($row,3) = 'COLUMN 3 Text'
$ExcelWorkSheet.Cells.Item($row,4) = "$out"
$ExcelWorkSheet.Cells.Item($row,5) = 'COLUMN 5 Text'
$ExcelWorkBook.Save()
$ExcelWorkBook.Close()
$Excel.Quit(
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel))
Stop-Process -Name EXCEL -Force

The following instruction does not return any value for $row for me:
$row = ($ExcelWorkSheet.UsedRange.Rows | ? { ($_.Value2 | ? {$_ -eq $null}).Count -eq 7 } | select -first 1).Row
I tried to browse the worksheet until a row where the first 7 columns are empty is found, using a loop. It seems to be working.
$lastrow = $ExcelWorkSheet.UsedRange.Rows.Count
for ($row = 1; $row -le $lastrow; $row ++) {
$test = $true
foreach ($col in 1..7) {$test = $test -and ($ExcelWorkSheet.Cells.Item($row,$col).Value2 -eq $null)}
if ($test) {break}
}

Related

Powershell - Convert list to UI with Out-Gridview

I have a script that read from excel and let the user to choose a column. The issue is that the list is not readable and I want to show the user the option to choose the version with UI with Out-Gridview
One more thing, I need that the answer will be a number
Here is the script:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
$columns = [ordered]#{}
for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
if ($name -ne $null){
$columns[$name] = $col}
}
$columns.GetEnumerator() | ForEach-Object {
# {0,2} means to write the index number from $_.Value right aligned for two digits
'{0,2}: {1}' -f $_.Value, $_.Name
}
do {
$answer = Read-Host "Please enter the number of the column you want to read from" #. Press Q to exit
# ask this question until the user enters a number or 'Q'
} until ($answer -eq 'Q' -or $answer -match '^\d{1,2}$')
switch ($answer) {
'Q' { break } # user wants to quit
{1..$columns.Count} {
# get the Name from the chosen value
$action = $columns.Keys | Where-Object {$columns["$_"] -eq $answer}
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
}
It looks like this:
To let the user select the tool version using Out-GridView, you need to build an array of objects, like below:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
# now, have the loop output objects that will be collected in variable $columns
$columns = for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
# if $name is not empty or whitespace only
if ($name -match '\S') {
[PsCustomObject]#{
Number = $col
Version = $name
}
}
}
# output to Out-GridView with -PassThru parameter so you can capture the selected item
$answer = ($columns | Out-GridView -Title 'Please select' -PassThru).Number
# if the user did not cancel
if ($answer) {
# get the Name from the chosen value
$action = $columns[$answer -1].Version
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
Please do not forget to remove the used COM objects from memory when the code is done, otherwise they will linger on..
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkSheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkBook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelObject)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

PowerShell - Open Excel and Unlock Certain Cells Based On Value

I have a report that I am running via SSRS to brings down some data that I need people to give comments on. I am attempting to write a PowerShell script that will open the file and unprotect certain cells based on their value.
This is what the report looks like:
The default for SSRS is that all cells are automatically protected. However, I would like to unprotect all the cells in column A (the column with the yellow text boxes) that do not have the value of "Manager Comments".
I am not really sure how to go about this. I have this so far for my script:
Param(
[string] $FolderPath,
[string] $FileName
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$excl=New-Object -ComObject "Excel.Application"
foreach ($file in $Files)
{
$wrkb=$excl.Workbooks.Open($file, 0, $false)
$wrkb.Worksheets("Comments").Range("A1:A100").Locked = $False
$wrkb.Save()
$wrkb.Close()
}
$excl.Quit()
But this will just unprotect all of column A. Does anyone know how to have it look at the values and only unprotect those blank values? Bonus points if it can just look for certain colors (the yellow).
And yes, I know that this is a terrible way of doing this...but I work with what I have.
A little progress update...I now can pull the value and cell color index in a loop, but I am not sure how to write the IF statement to lock the cell if the $Color value is equal to "36".
Param(
[string] $FolderPath = "C:\Root\Data\VisualCron\Development\",
[string] $FileName = "Combined Master Report.xlsx"
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$SheetName = "Comments"
$Excel = New-Object -ComObject "Excel.Application"
foreach ($File in $Files)
{
$Workbook = $Excel.Workbooks.Open($File, 0, $False)
$Sheet = $Workbook.Worksheets.Item("Comments")
$MaxRow = ($Sheet.UsedRange.Rows).Count
$Row,$Column = 1,1
for ($i = 0; $i -le $MaxRow - 1; $i++)
{
$Color = $Sheet.Cells.Item($Row+$i,$Column).Interior.ColorIndex
$Value = $Sheet.Cells.Item($Row+$i,$Column).Text
Write-Host ("Color Value:" + $Color + " " + $i + " " + $Value)
}
#$Workbook.Worksheets("Comments").Range("A3:A50").Locked = $False
#$Workbook.Worksheets("Comments").Protect('Test',1,1,1,1,0,0,0,0,0,0,0,0,0,0,0)
$Workbook.Save()
$Workbook.Close()
}
$excl.Quit()
Another update:
I have gotten it to work, but it is a little strange. When I first go through my loop, the color index appears to be "36", but once I change one value, the yellow index changes to "5". I am not really sure why it does that, but I wrote this script, and hopefully, it is consistent.
Param(
[string] $FolderPath = "C:\Root\Data\VisualCron\Development\",
[string] $FileName = "Combined Master Report.xlsx"
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$SheetName = "Comments"
$Excel = New-Object -ComObject "Excel.Application"
foreach ($File in $Files)
{
$Workbook = $Excel.Workbooks.Open($File, 0, $False)
$Sheet = $Workbook.Worksheets.Item("Comments")
$MaxRow = ($Sheet.UsedRange.Rows).Count
$Row,$Column = 1,1
for ($i = 1; $i -le $MaxRow - 1; $i++)
{
$Color = $Sheet.Cells.Item($Row+$i,$Column).Interior.ColorIndex
$Value = $Sheet.Cells.Item($Row+$i,$Column).Text
#Write-Output ($Color)
$Range = "A" + ($Row + $i) + ":A" + ($Row + $i)
if ($Color -eq 36 -or $Color -eq 5)
{
$Range = "A" + ($Row + $i) + ":A" + ($Row + $i)
$Workbook.Worksheets("Comments").Range($Range).Locked = $False
#Write-Host $Range
}
}
$Workbook.Worksheets("Comments").Protect('Test',1,1,1,1,0,0,0,0,0,0,0,0,0,0,0)
$Workbook.Save()
$Workbook.Close()
}
$Excel.Quit()
Does anyone have any ideas on why the color index would be changing?

Replacing multiple texts not getting saved

I've a bunch of files in which I need to replace content like for e.g. wherever there is 'AA' I need to replace with 'E1', 'A1' with 'P4'. The same content needs to be changed differently in different files. So for example in the 2nd file 'AA' would become 'P1', 'A1' would become 'E1', etc. To accomplish this I've an Excel sheet with 2 columns like the below:
TC CodeChange
086 AA-E1; A1-P2
099 AA-P2; A1-E1; A2-E2; Z3-E3
100 AA-P2; A1-E2; A2-E3; Z3-O3
PowerShell script which I wrote for the above:
Script 1:
function func3 {
Param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output = $_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\...\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")) {
(Get-Content $inputfile.FullName) | ForEach-Object {
$_ -replace "$($arry1[$j].Split('-')[0])","$($arry1[$j].Split('-')[1])"
} | Set-Content $inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
Script 2:
function func3 {
param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output=$_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).Count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")){
$content = [System.IO.File]::ReadAllText($inputfile.FullName).Replace($arry1[$j].Split('-')[0], $arry1[$j].Split('-')[1])
[System.IO.File]::WriteAllText($inputfile.FullName, $content)
Write-Host $arry1[$j].Split('-')[0]' replaced with '$arry1[$j].Split('-')[1]' in file: '$inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
The folder where the files are has the files having names containing the same digits in the 'TC' column in my Excel sheet. Example:
TC 086.txt
TC 099.txt
etc.
That way after I import the contents of the Excel into a hashtable I extract the digits from the filenames and get the corresponding values for the same key in the hashtable. For example the value for the key '086' from the hashtable would be 'AA-E1; A1-P2'. Then I split the items to be replaced from the hashtable value (separated by ;) and then store that in an array. The using a loop I try to replace the contents of each file based on the data retrieved from the spreadsheet.
The issue I'm facing with both the approaches is that only the 1st item in each file is getting replaced. The rest of the items are not getting replaced. For example only 'AA' value in file 'TC 086.txt' is getting replaced with 'E1'. 'A1' is not getting replaced with 'P2'.
I found out what the issue was. I basically had to trim the elements of the array
$arry1
after splitting them (separated by ;) and before passing them as parameters to the 'Replace' function. Apparently there was a space before every element in that array except the 1st element (that's how they were stored in the source: excel spreadsheet). Hence the 'Replace' method was not finding that element in the file and hence not replacing it. Removing the spaces before the elements solved the issue

Convert code from Excel Macro to PowerShell

I open a CSV file in Excel and run this macro to change the background color. I am trying to convert this part of code to PowerShell.
lrow = Range("G" & Rows.Count).End(xlUp).Row
Set MR = Range("G2:G" & lrow)
For Each cell In MR
If UCase(Trim(cell.Value)) = "FALSE" Then
cell.Interior.ColorIndex = 3
End If
Next
Any help converting this code to PowerShell.
Thanks
SR
You could write something like this:
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $true
$objExcel.DisplayAlerts = $false
$filePath = "c:\logs\2015-04-23.csv"
$xlsFilePath = Get-Item -Path $filePath | % { Join-Path (Split-Path $_ -Parent) "$($_.BaseName).xls" }
$workBook = $objExcel.Workbooks.Open($filePath)
$workSheet = $WorkBook.sheets | select -First 1
$xlup = -4162
$lrow = $workSheet.cells.Range("G" + $workSheet.Rows.Count).End($xlup).Row
$workSheet.cells.Range("G2:G" + $lrow) | % {
$value = $_.Text
if($value.ToUpper() -eq "TRUE"){
$_.Interior.ColorIndex = 3
}
}
$WorkBook.SaveAs($xlsFilePath, 18)
$objExcel.Quit()
If you have a very large file, it is faster to search values using powershell then updating the Excel sheet. The following example looks a bit funny but executes much faster.
$filePath = "c:\logs\2015-04-23.csv"
$rowAliases = 97..122 | foreach { ([char]$_).ToString().ToUpper() }
$selectedRow = "G"
$selectedName = (Get-Content $filePath -ReadCount 1 -TotalCount 1).Split(",")[$rowAliases.IndexOf($selectedRow)]
$startRow = 2
$rowCount = 1;
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $true
$objExcel.DisplayAlerts = $false
$xlsFilePath = Get-Item -Path $filePath | % { Join-Path (Split-Path $_ -Parent) "$($_.BaseName).xls" }
$workBook = $objExcel.Workbooks.Open($filePath)
$workSheet = $WorkBook.sheets | select -First 1
Import-Csv -Path $filePath | % {
if($rowCount -ge $startRow){
[string]$value = $_ | select -ExpandProperty $selectedName
if($value.ToUpper() -eq "TRUE"){
$workSheet.cells.Item($rowCount + 1, $selectedIndex + 1).Interior.ColorIndex = 3
}
}
$rowCount ++
}
$WorkBook.SaveAs($xlsFilePath, 18)
$objExcel.Quit()

Pulling Columns from Excel

ExampleData
I'm trying to pull from a total of 6 columns from an excel worksheet. I'm using a modified script from another post on stack, it looks like this.
$strPath = "C:\Users\User\Documents\EXEL\fj.xls"
$AssetInv = "C:\Users\User\Documents\EXEL\fj.txt"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($strPath)
$worksheet = $workbook.sheets.item("Daily Price Quote - Better Of -")
$intRowMax = ($worksheet.UsedRange.Rows).count
#$StartRow = 2
#$site = 1
#$state = 3
#$retailprice = 18
#$yourprice = 20
#$SavingsTotal = 21
Write "`Site City State retailprice yourprice savingstotal" | Out-File $AssetInv
Write "--------------- ------------------- -------------------- -----------------------------" |
Out-FIle $AssetInv -Append
Write-Host "Processing: " $intRowMax "rows"
for ($intRow = 7 ; $intRow -le $intRowMax ; $intRow++) {
$site = $worksheet.cells.item($intRow, 10).value2
$city = $worksheet.cells.item($intRow, 2).value2
$state = $worksheet.cells.item($intRow, 3).value2
$retailprice = $worksheet.cells.item($intRow, 18).value2
$yourprice = $worksheet.cells.item($intRow, 20).value2
$SavingsTotal = $worksheet.cells.item($intRow, 21).value2
if (($site -ge 1 )) {
"{0, -15} {1, -30} {2, -25} {3, -25}" -f $site, $city, $state, $retailprice, $yourprice, $Savingstotal |
Out-File $AssetInv -Append
}
}
$objexcel.quit()
Currently I am pulling no data, earlier I had it working for a maximum of 3 records. Any insight as to what I am doing wrong?
Ok, we'll start with your code to get the worksheet we want. Then we'll select the UsedRange to get the range of cells that have data. Then we'll run that through a ForEach loop, skipping the first 5 rows since they have garbage/header info. For each row we'll make a new object, with properties set to the associated cell (kind of like what you do with all your variables). All of those objects will be collected in an array, which we output to a CSV file. If you don't like the CSV, you could pipe it to Format-Table, and pipe that to Out-File (might have to pipe to out-string, then out-file... it's not something I do often).
$strPath = "C:\Users\User\Documents\EXEL\fj.xls"
$AssetInv = "C:\Users\User\Documents\EXEL\fj.txt"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($strPath)
$worksheet = $workbook.sheets.item("Daily Price Quote - Better Of -")
$UsedRange = $worksheet.usedrange
$Data = ForEach($Row in ($UsedRange.Rows|Select -skip 5)){
New-Object PSObject -Property #{
'Site' = $Row.Cells.Item(1).Value2
'City' = $Row.Cells.Item(2).Value2
'State' = $Row.Cells.Item(3).Value2
'Retail Price' = $Row.Cells.Item(18).Value2
'Your Price' = $Row.Cells.Item(20).Value2
'Total Savings' = $Row.Cells.Item(21).Value2
}
}
$Data | Where{[int]::Parse($_.Site) -ge 1} | Select Site,City,State,'Retail Price','Your Price','Total Savings' | Export-Csv -NoTypeInformation -Path $AssetInv
$objExcel.quit()
The plus side is that you also are left with $Data which is all the data you want to be able to use in case you need to do anything else with it (look for items that you save over X%, or items that cost less than $5 or whatever.

Resources