Search strings in Excel Column & Delete the column in PowerShell - powershell

I am new to this forum so please excuse if this is not the right channel.
I am looking for some help in searching the strings which are in Excel Column Header "$Col4 / $Col6 / $Col7 / $Col19 / $Col22 / $Col27" and if found, then delete the entire column.
The concern is if I am changing the line as per below then it goes into loop but reads only Row 1 of the sheet. But if I keep as per the below code then because it reads all the $col value as Row 1, it gives the result as "Nothing found".
if (($sheet.cells.Item($row,$column).text -eq $Col4) -or ($sheet.cells.Item($row,$column).text -eq $Col6) -or ($sheet.cells.Item($row,$column).text -eq $Col7) -or ($sheet.cells.Item($row,$column).text -eq $Col19) -or ($sheet.cells.Item($row,$column).text -eq $Col22) -or ($sheet.cells.Item($row,$column).text -eq $Col27)) {
$column++
$Col4 = 'Col4'
$Col6 = 'Col6'
$Col7 = 'Col7'
$Col19 = 'Col19'
$Col22 = 'Col22'
$Col27 = 'Col27'
$Results = "C:\Temp\Results.xls"
$excel = New-Object -Com Excel.Application -Property #{Visible = $false}
# open Excel file
$workbook = $excel.Workbooks.Open($Results)
$sheet = $workbook.ActiveSheet
$row = 1
$column = 1
$found = $false
# Search for /TESTOut in A1
$info = $sheet.cells.Item($column, $row).Text
# Write-Host $info
if($info -eq "/Test"){
$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the first row
}
$WorksheetRange = $sheet.UsedRange
$RowCount = $WorksheetRange.Rows.Count
$ColumnCount = $WorksheetRange.Columns.Count
While ($row -ne $RowCount) {
If ($sheet.cells.Item($row,$column).text -eq $Col4) {
$column++
If ($sheet.cells.Item($row,$column).text -eq $Col6) {
$column++
If ($sheet.cells.Item($row,$column).text -eq $Col7) {
$column++
If ($sheet.cells.Item($row,$column).text -eq $Col19) {
$column++
If ($sheet.cells.Item($row,$column).text -eq $Col22) {
$column++
If ($sheet.cells.Item($row,$column).text -eq $Col27) {
Write-Host -ForegroundColor Green "Found match at Row: " $row # Replace this line to delete the Column
$found =$true
}
}
}
}
}
}
Else {
$row++
$column = 1
}
}
If (!($found)) {
Write-Host -ForegroundColor Red "Nothing found" # This is for test purpose only. Do Nothing.
}
$workbook.Close($true) # Close workbook and save changes
$excel.Quit() # Quit Excel
[Runtime.Interopservices.Marshal]::ReleaseComObject($excel) # Release COM

This could be done with a simple counting loop running backwards:
# columns to delete
$deleteThese = 'Col4', 'Col6', 'Col7', 'Col19', 'Col22', 'Col27'
$file = 'C:\Temp\Results.xls'
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
# open the Excel file
$workbook = $excel.Workbooks.Open($file)
$sheet = $workbook.ActiveSheet
# get the number of columns in the sheet
$colMax = $sheet.UsedRange.Columns.Count
# loop through the columns to test if the column header is found in the $deleteThese array
# do the loop BACKWARDS, otherwise the indices will change on every deletion.
for ($col = $colMax; $col -ge 1; $col--) {
$header = $sheet.Cells.Item(1, $col).Value2
if ($deleteThese -contains $header) {
$null = $sheet.Columns($col).EntireColumn.Delete()
}
}
# save and exit
$workbook.Close($true)
$excel.Quit()
# clean up the COM objects used
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Related

I want to replace text in FieldCodes (Word Document). How can i use variable for that?

I want to replace text in FieldCodes (Word Document). How can i use variables for that?
This is for a Word Doc with links to other Word Doc's (IncludeText Link). When i change the link one by one without variable it works. When i use variables for it, it doesn't.
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$Test = 147 # (Test.GetType() = Int32)
$Document.Fields(147) #Works
$Document.Fields($Test) #Works
$Document.Fields($Test).LinkFormat.SourceFullName = "" #Works
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
#TextLinks contains value 147 and 149
$Test = $TextLinks[0] # is also 147 (Test.GetType() = Int32)
$Document.Fields($Test) #Doesn't work (runs indefinitely)
$Document.Fields($Test).LinkFormat.SourceFullName = "" #Doesn't work (runs indefinitely)
147..149 | Foreach { $Document.Fields($_).LinkFormat.SourceFullName } #Doesn't work (runs indefinitely)
Update:
Now it runs with $Test = [INT]$Textlinks[0]. Thanks Cindy!
But when i try a loop it hangs with te second value
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
$ItemNumber = 0
$End = $TextLinks.Count
Do {
$Item = [INT]$Textlinks[$ItemNumber]
if ($Document.Fields($Item).LinkFormat.SourceFullName -match "Test") {
$Link = $Document.Fields($Item).LinkFormat.SourceFullName -replace "Test", "TestTest"
$Document.Fields($Item).LinkFormat.SourceFullName = $Link
$Document.Fields($Item).LinkFormat.AutoUpdate = "True"
$ItemNumber += 1
}
} Until ($ItemNumber -eq $End)
$Document.Save()
$Word.Quit()
$OUT=[System.Runtime.InteropServices.Marshal]::ReleaseComObject($Word)
Update 2:
Code below runs fine but i dont understand why the code above doesn't
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
$ItemNumber = ($TextLinks.Count)-1
$End = -1
Do {
$Item = [INT]$Textlinks[$ItemNumber]
if ($Document.Fields($Item).LinkFormat.SourceFullName -match "Test") {
$Link = $Document.Fields($Item).LinkFormat.SourceFullName -replace "Test", "TestTest"
$Document.Fields($Item).LinkFormat.SourceFullName = $Link
$Document.Fields($Item).LinkFormat.AutoUpdate = "True"
$ItemNumber -= 1
}
} Until ($ItemNumber -eq $End)
$Document.Save()
$Word.Quit()
$OUT=[System.Runtime.InteropServices.Marshal]::ReleaseComObject($Word)

How to loop in Excel to apply GPOs list to OUs in Powershell

I need to loop through an Excel file for OUs and GPOs then apply them to the GPO Management Console. I'm stuck for understand the loop process.
get-module activedirectory,grouppolicy
#Open Excel and read info in file
$filepath = "C:\temp\AllGPOsLinkWin10toApply-report date 13-05-2019.xlsx"
$sheetname = "AllGPOsWin10 date 13-05"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($filepath)
$WorkSheet = $WorkBook.Sheets.Item($sheetname)
$OU = #{}
$destinationOU = #{}
$i = 1
$j= 2
#Read the OU cells (A,$i) column 1, row 1 to 13
$destinationOU = $WorkSheet.Cells.Item($i, 1).Text
#Read the GPO cells (all cells to the right of the OU cell)
#then loop and apply each GPO to the OU until cells are empty
$GPO = $WorkSheet.Cells.Item($i+1, $j).Text
set-GPLink -Name $GPO -Target $destinationOU -LinkEnabled yes
i = i + 1
#Loop all OUs cells in column A and start again the process
I suggest this:
#Loop from row 1 to 13
for ($row = 1; $row -le 13; $row++) {
$destinationOU = $WorkSheet.Cells.Item($row, 1).Text
#Read all the cells to right of column 2 (until an empty cell is found)
$col = 2
while (($WorkSheet.Cells.Item($row, $col).Text) -ne "") {
$GPO = $WorkSheet.Cells.Item($row, $col).Text
set-GPLink -Name $GPO -Target $destinationOU -LinkEnabled yes
$col++
}
}
It may be safer to loop through the used range:
for ($row = 1; $row -le $WorkSheet.UsedRange.Row.Count; $row++) {
$destinationOU = $WorkSheet.Cells.Item($row, 1).Text
for ($col = 2; $col -le $WorkSheet.UsedRange.Columns.Count; $col++) {
$GPO = $WorkSheet.Cells.Item($row, $col).Text
set-GPLink -Name $GPO -Target $destinationOU -LinkEnabled yes
}
}

Extract sections from range found in word document

Below code works fine, we got start and end point which needs to be extracted but im not able to get range.set/select to work
I'm able to get the range from below, just need to extra and save it to CSV file...
$found = $paras2.Range.SetRange($startPosition, $endPosition) - this piece doesn't work.
$file = "D:\Files\Scan.doc"
$SearchKeyword1 = 'Keyword1'
$SearchKeyword2 = 'Keyword2'
$word = New-Object -ComObject Word.Application
$word.Visible = $false
$doc = $word.Documents.Open($file,$false,$true)
$sel = $word.Selection
$paras = $doc.Paragraphs
$paras1 = $doc.Paragraphs
$paras2 = $doc.Paragraphs
foreach ($para in $paras)
{
if ($para.Range.Text -match $SearchKeyword1)
{
Write-Host $para.Range.Text
$startPosition = $para.Range.Start
}
}
foreach ($para in $paras1)
{
if ($para.Range.Text -match $SearchKeyword2)
{
Write-Host $para.Range.Text
$endPosition = $para.Range.Start
}
}
Write-Host $startPosition
Write-Host $endPosition
$found = $paras2.Range.SetRange($startPosition, $endPosition)
# cleanup com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($doc) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
This line of code is the problem
$found = $paras2.Range.SetRange($startPosition, $endPosition)
When designating a Range by the start and end position it's necessary to do so relative to the document. The code above refers to a Paragraphs collection. In addition, it uses SetRange, but should only use the Range method. So:
$found = $doc.Range.($startPosition, $endPosition)

How PowerShell Sum an Excel column and print the result?

I have an excel file with 10 columns. I want to get the sum of the column with header "Sales" and print it on the console.
How this can be done with PowerShell? I am using the below code but I do not know how to replace H with $i in the following expression:
='=SUM(H1:H'+$RowCount')'
Where H is column "Sales"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $False
$NewWorkbook = $Excel.Workbooks.open("C:\Test.xlsx")
$NewWorksheet = $NewWorkbook.Worksheets.Item(1)
$NewWorksheet.Activate() | Out-Null
$NewWorksheetRange = $NewWorksheet.UsedRange
$RowCount = $NewWorksheetRange.Rows.Count
$ColumnCount = $NewWorksheetRange.Columns.Count
for ($i = 1; $i -lt $ColumnCount; $i++)
{
if ($NewWorksheet.cells.Item(1,$i).Value2 -eq "Sales")
{
$NewWorksheet.Cells.Item($RowCount+2,$i)='=SUM(H1:H'+$RowCount')'
Write-Host $NewWorksheet.Cells.Item($RowCount+1,$i).Value2
}
}
$Excel.Application.DisplayAlerts=$False
$NewWorkbook.SaveAs("C:\Test_New.xlsx")
$NewWorkbook.close($false)
$Excel.quit()
spps -n excel
I have replaced:
$NewWorksheet.Cells.Item($RowCount+2,$i) ='=SUM(H1:H'+(1+$RowCount)+')'
Write-Host $NewWorksheet.Cells.Item($RowCount+2,$i).Value2
with:
$FirstCell = $NewWorksheet.Cells(2,$i).Address('+True, False+')
$LastCell = $NewWorksheet.Cells(1+$RowCount,$i).Address('+True, False+')
$NewWorksheet.Cells.Item($RowCount+2,$i)='=SUM('+$FirstCell+':'+$LastCell+')'
Write-Host $NewWorksheet.Cells.Item($RowCount+2,$i).Value2
I can highly recommend the PowerShell-Module "ImportExcel". This Modules enables you to import Excel-Files as easy as with Import-Csv
Without knowing much about your files/enviroment, you could try something like this:
foreach ($data in (Import-Excel "$PSScriptRoot\test.xlsx")) {
$result += $data.Sales
}
Write-Host $result

PowerShell workaround for the blank Header/Footer bug when Find and Replace in a whole Word document

I am trying to put together a PowerShell script to do multiple find and replace throughout a whole Word Document, that is including Headers, Footers and any Shape potentially displaying text.
There are plenty of VBA examples around so it's not too difficult, but there is a know bug that is circumvented in VBA with a solution dubbed as "Peter Hewett 's VBA trickery". See this example and also this one.
I have tried to address this bug in a similar fashion in PowerShell but it is not working as expected. Some TextBoxes in Header or Footer are still being ignored.
I noticed however, that runnning my script twice will actually end up working.
Any idea as to a solution to this problem would be greatly appreciated.
$folderPath = "C:\Users\user\folder\*" # multi-folders: "C:\fso1*", "C:\fso2*"
$fileType = "*.doc" # *.doc will take all .doc* files
$textToReplace = #{
# "TextToFind" = "TextToReplaceWith"
"This1" = "That1"
"This2" = "That2"
"This3" = "That3"
}
$word = New-Object -ComObject Word.Application
$word.Visible = $false
$storyTypes = [Microsoft.Office.Interop.Word.WdStoryType]
#Val, Name
# 1, wdMainTextStory
# 2, wdFootnotesStory
# 3, wdEndnotesStory
# 4, wdCommentsStory
# 5, wdTextFrameStory
# 6, wdEvenPagesHeaderStory
# 7, wdPrimaryHeaderStory
# 8, wdEvenPagesFooterStory
# 9, wdPrimaryFooterStory
# 10, wdFirstPageHeaderStory
# 11, wdFirstPageFooterStory
# 12, wdFootnoteSeparatorStory
# 13, wdFootnoteContinuationSeparatorStory
# 14, wdFootnoteContinuationNoticeStory
# 15, wdEndnoteSeparatorStory
# 16, wdEndnoteContinuationSeparatorStory
# 17, wdEndnoteContinuationNoticeStory
Function findAndReplace($objFind, $FindText, $ReplaceWith) {
#simple Find and Replace to execute on a Find object
$matchCase = $true
$matchWholeWord = $true
$matchWildcards = $false
$matchSoundsLike = $false
$matchAllWordForms = $false
$forward = $true
$findWrap = [Microsoft.Office.Interop.Word.WdReplace]::wdReplaceAll
$format = $false
$replace = [Microsoft.Office.Interop.Word.WdFindWrap]::wdFindContinue
$objFind.Execute($FindText, $matchCase, $matchWholeWord, $matchWildCards, $matchSoundsLike, $matchAllWordForms, \`
$forward, $findWrap, $format, $ReplaceWith, $replace) > $null
}
Function findAndReplaceAll($objFind, $FindText, $ReplaceWith) {
findAndReplace $objFind $FindText $ReplaceWith
While ($objFind.Found) {
findAndReplace $objFind $FindText $ReplaceWith
}
}
Function findAndReplaceMultiple($objFind, $lookupTable) {
#apply multiple Find and Replace on the same Find object
$lookupTable.GetEnumerator() | ForEach-Object {
findAndReplaceAll $objFind $_.Key $_.Value
}
}
Function findAndReplaceMultipleWholeDoc($Document, $lookupTable) {
ForEach ($storyRge in $Document.StoryRanges) {
#Loop through each StoryRange
Do {
findAndReplaceMultiple $storyRge.Find $lookupTable
#check if the StoryRange has shapes (we check only StoryTypes 6 to 11, basically Headers and Footers)
# as the Shapes inside the wdMainTextStory will be checked
# see http://wordmvp.com/FAQs/Customization/ReplaceAnywhere.htm
# and http://gregmaxey.com/using_a_macro_to_replace_text_wherever_it_appears_in_a_document.html
If (($storyRge.StoryType -ge $storyTypes::wdEvenPagesHeaderStory) -and \`
($storyRge.StoryType -le $storyTypes::wdFirstPageFooterStory)) {
If ($storyRge.ShapeRange.Count) { #non-zero is True
ForEach ($shp in $storyRge.ShapeRange) {
If ($shp.TextFrame.HasText) { #non-zero is True, in case of text .HasText = -1
findAndReplaceMultiple $shp.TextFrame.TextRange.Find $lookupTable
}
}
}
}
#check for linked Ranges
$storyRge = $storyRge.NextStoryRange
} Until (!$storyRge) #non-null is True
}
}
Function processDoc {
$doc = $word.Documents.Open($_.FullName)
# The "VBA trickey" translated to PowerShell...
$junk = $doc.Sections.Item(1).Headers.Item(1).Range.StoryType
#... but not working
findAndReplaceMultipleWholeDoc $doc $textToReplace
$doc.Close([ref]$true)
}
$sw = [Diagnostics.Stopwatch]::StartNew()
$countf = 0
Get-ChildItem -Path $folderPath -Recurse -Filter $fileType | ForEach-Object {
Write-Host "Processing \`"$($_.Name)\`"..."
processDoc
$countf++
}
$sw.Stop()
$elapsed = $sw.Elapsed.toString()
Write-Host "Done. $countf files processed in $elapsed"
$word.Quit()
$word = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I checked out Microsoft documentation documentation here and then I think the below code can do it.
$word = New-Object -ComObject Word.Application
$word.visible=$false
$files = Get-ChildItem "C:\Users\Ali\Desktop\Test" -Filter *.docx
$find="Hello"
$replace="Bye"
$wdHeaderFooterPrimary = 1
$ReplaceAll = 2
$FindContinue = 1
$MatchCase = $false
$MatchWholeWord = $false
$MatchWildcards = $false
$MatchSoundsLike = $false
$MatchAllWordForms = $false
$Forward = $true
$Wrap = $findContinue
$Format = $false
for ($i=0; $i -lt $files.Count; $i++) {
$filename = $files[$i].FullName
$doc = $word.Documents.Open($filename)
ForEach ($StoryRange In $doc.StoryRanges){
$StoryRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
While ($StoryRange.find.Found){
$StoryRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
}
While (-Not($StoryRange.NextStoryRange -eq $null)){
$StoryRange = $StoryRange.NextStoryRange
$StoryRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
While ($StoryRange.find.Found){
$StoryRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
}
}
}
#shapes in footers and headers
for ($j=1; $j -le $doc.Sections.Count; $j++) {
$FooterShapesCount = $doc.Sections($j).Footers($wdHeaderFooterPrimary).Shapes.Count
$HeaderShapesCount = $doc.Sections($j).Headers($wdHeaderFooterPrimary).Shapes.Count
for ($i=1; $i -le $FooterShapesCount; $i++) {
$TextRange = $doc.Sections($j).Footers($wdHeaderFooterPrimary).Shapes($i).TextFrame.TextRange
$TextRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
}
for ($i=1; $i -le $HeaderShapesCount; $i++) {
$TextRange = $doc.Sections($j).Headers($wdHeaderFooterPrimary).Shapes($i).TextFrame.TextRange
$TextRange.Find.Execute($find,$MatchCase,
$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
$MatchAllWordForms,$Forward,$Wrap,$Format,
$replace,$ReplaceAll)
}
}
$doc.Save()
$doc.close()
}
$word.quit()