I am trying to automate the process of importing macros in Word documents, but I fail to load macros in any macro-enabled word document. It works just for the Normal.dotm
I want to create a new doc, and load the macro from a .bas file.
$basPath = "C:\Files\macro.bas"
$docPath = "C:\Files\macro_document.doc"
$word = New-Object -ComObject "Word.Application"
$document = $word.documents.Add()
$document.SaveAs($docPath)
$document.VBProject.VBComponents.Import($basPath)
$document.Save()
$document.Close()
$word.Quit()
What am I doing wrong?
Use .SaveAs($docPath,0) where 0 is wdFormatDocument 97-2003
$basPath = "C:\Files\macro.bas"
$docPath = "C:\Files\macro_document.doc"
$word = New-Object -ComObject "Word.Application"
$document = $word.Documents.Add()
$document.VBProject.VBComponents.Import($basPath)
$document.SaveAs($docPath,0) # 0 = wdFormatDocument 97-2003
$document.Close()
$word.Quit()
Or use extension .docm and .SaveAs($docPath,13) where 13 is wdFormatXMLDocumentMacroEnabled:
$basPath = "C:\Files\macro.bas"
$docPath = "C:\Files\macro_document.docm"
$word = New-Object -ComObject "Word.Application"
$document = $word.Documents.Add()
$document.VBProject.VBComponents.Import($basPath)
$document.SaveAs($docPath,13) # 13 = wdFormatXMLDocumentMacroEnabled
$document.Close()
$word.Quit()
Related
Double Clicking on Pivot tables GrandTotal data creates a separate sheet with entire data source. Would like to get this done through powershell. Below is the code I tried with Powershell 5.1.
$excelFile = "C:\test\Testfile.xlsb"
$Excel = New-Object -ComObject Excel.Application
$wb = $Excel.Workbooks.Open($excelFile)
$s=$wb.worksheets(1).range("C7").select
$s.showdetail
$wb.saveas("C:\test\Testfile_modified.xlsb")
$wb.close()
$Excel.quit()
More direct - no need for select or intermediate variable $s:
$excelFile = "C:\tester\Testfile.xlsb"
$Excel = New-Object -ComObject Excel.Application
$wb = $Excel.Workbooks.Open($excelFile)
$wb.worksheets(1).range("C7").showdetail = $true
$wb.saveas("C:\tester\Testfile_modified.xlsb")
$wb.Close()
$Excel.Quit()
#make sure Excel process is ended (or it may persist)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
Remove-Variable Excel
in a script file called from cmd using:
powershell -noexit "& ""C:\Tester\psExcelTest.ps1"""
I am currently working on an automation which requires to export sql results to excel data. I want to do this via SQL query. Few options i know are as below, but before i start exploring these things. I would like to know the best possible approach .
PS - It would be really great if there is a way to dynamically create excel during query execution and export data in multiple excel sheets.
OPENROWSET
bcp_cmd
You can CREATE VIEW and utilize that view in Excel 2016 under its data connections through PowerQuery. Views are preferred since they are managed independently in the server, and provide realtime data results without requiring a full query to be embedded in the Excel file. The resulting set exists in the workbook as a refresh-able table. Results that need to be recorded should be done via new workbooks or UPDATE's back to the server in a separate script.
In the PowerQuery Editor, Home tab, click Advanced Editor. The database connection string and call to the server is below. You can also dynamically pass parameters from an Excel table to the query utilizing a table in the Name Manager.
Excel tab, table name: tbl_Parameters
A B
1 StartDate 01/01/2020
2 EndDate 02/01/2020
let
Source = Sql.Database("ServerName" , "Database", [Query="
DECLARE #Start_Date AS datetime
DECLARE #End_Date AS datetime
SET #Start_Date = '"&StartDate&"'
SET #End_Date = '"&EndDate&"'
SELECT * FROM uvw_product
WHERE item_sold_date BETWEEN
#Start_Date AND #End_Date
"])
in
Source
A while back I cobbled this Powershell script together.
It querys sql server data, saves as csv, formats it and saves as xls, then mails via smtp.
You can set up a windows scheduled task to automate it.
There's also an import xls module for powershell.
Import-Module Sqlps -DisableNameChecking;
#execute mysql query as excel
$Server = "DB SERVER";
$Database = "DBNAME";
$Query = #"
*SELECT QUERY HERE*
"#
$a = Get-Date
#note: if you run get-location from ide, it will use the ide path instead of the script path
$currentLocation = Split-Path -Parent $PSCommandPath
$FilePath = $currentLocation + "\CSVName.csv"
$SavePath = $currentLocation + "\XLSFileName" +$a.Day+ $a.Month + $a.Year + ".xls"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $Server; Database = $Database; User ID = DBUSERNAME; Password = PASSWORD";
$SqlConnection.Open()
$sqlcmd = $SqlConnection.CreateCommand()
$sqlcmd.Connection = $SqlConnection
$sqlcmd.CommandText = $Query
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $sqlcmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$DataSet.Tables[0] | Export-Csv -notypeinformation $FilePath
$SqlConnection.Close()
#Invoke-Sqlcmd -Query $Query -ConnectionString $SqlConnection.ConnectionString | Export-Csv -notypeinformation $FilePath
#release memory function
function Release-Ref($ref){
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#format excel to display correct date
$objExcel = new-object -comobject excel.application
$objWorkbook = $objExcel.Workbooks.open($FilePath)
$objWorksheet = $objWorkbook.Worksheets.Item(1)
$objRange = $objWorksheet.UsedRange
[void] $objRange.EntireColumn.Autofit()
$objWorkbook.Saved = $True
$objExcel.DisplayAlerts = $False
$objWorkbook.SaveAs($SavePath,1)
$objExcel.Quit()
#release memory
Release-Ref($objWorksheet)
Release-Ref($objWorkbook)
Release-Ref($objExcel)
#create mail
$smtpServer = "SMTPSERVER"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$att = new-object Net.Mail.Attachment($SavePath)
$msg = new-object Net.Mail.MailMessage
$msg.Subject = "EMAIL SUBJECT"
$msg.From = "FROM EMAIL"
#$msg.To.Add("TO EMAIL 1")
$msg.To.Add("TO EMAIL 2")
$msg.Body = #"
Hi,
MSG BODY HERE
Best Regards
"#
$msg.Attachments.Add($att)
$smtp.Send($msg)
$att.Dispose()
Download and install the 64-bit or 32-bit version of the driver, based on what you have installed.
https://www.microsoft.com/en-us/download/details.aspx?id=13255
Then, you should be able to run this.
insert into OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 8.0;Database=D:\testing.xls;',
'SELECT * FROM [SheetName$]') select * from SQLServerTable
Notice: this may not work if you have one 32-bit technology and one 64-bit technology. In this case, you may need to employ some kind of workaround.
See this link for additional ideas of how to integrate SQL Server and Excel.
https://solutioncenter.apexsql.com/how-to-import-and-export-sql-server-data-to-an-excel-file/
I have to create a Powershell script that reads a Word file (.docx) and replaces strings by hyperlinks. So far, based on this script, I can replace all occurrences of a string by another string easily. With that script, I can look for a string and replace it by an hyperlink. However, only the first occurrence is replaced.
Here's my understanding of the problem so far :
The first script uses the ReplaceWith and Replace=wdReplaceAll parameters of the Execute function of the Find Interface. The issue is that ReplaceWith expects a String and not an Hyperlink object.
The second script doesn't specify those parameters so it only uses the Find.Execute() function to move the start of the Range object to the found string and then insert a link at that position.
Since I can't replace all occurrences at once, I'd try to iterate through all matches to insert links at their location. But Find.Execute() only returns a Boolean... Now I'm thinking of maybe redefining the range to exclude the found occurrence and looping until the end of the doc, but this feels complicated.
Let's say I've got a Word file with this text :
In other words, each of the articles linked here is an index to
multiple lists on a topic. Some of the linked articles are themselves
lists of lists of lists. This article is also a list of lists.
Here's a bare bone script that replace only the first occurrence of "lists" by a relative link. I'm trying to replace all occurrences of "lists" to the hyperlink $linkPath, but can't find how. Help ?
Add-Type -AssemblyName "Microsoft.Office.Interop.Word"
$wdunits = "Microsoft.Office.Interop.Word.wdunits" -as [type]
$objWord = New-Object -ComObject Word.Application
$objWord.Visible = $false
# Text to find and replace by a link
$findText = "lists"
# Link to file
$linkPath = ".\Untitled.png"
# Source Word (2007+) file
$objDoc = $objWord.Documents.Open([FILE TO READ FROM])
# Resulting file
$saveAs = [FILE TO SAVE TO]
# Set Range to all document content
$range = $objDoc.Content
$range.movestart($wdunits::wdword,$range.start) | Out-Null
# Execute params
$matchCase = $false
$matchWholeWord = $true
$matchWildcards = $false
$matchSoundsLike = $false
$matchAllWordForms = $false
$forward = $true
$wrap = 1
$format = $False
$wdReplaceNone = 0
$wdFindContinue = 1
$wdReplaceAll = 2
# $wordFound is true is $findText is found in $range.
# $range.find.execute modifies the start of the range
$wordFound = $range.find.execute($findText,$matchCase,`
$matchWholeWord,$matchWildCards,$matchSoundsLike,`
$matchAllWordForms,$forward,$wrap)
if($wordFound){
$objDoc.Hyperlinks.Add($range,$linkPath,$null,$null,$findText) | Out-Null
}
$objDoc.SaveAs($saveAs)
$objDoc.Close()
$objWord.Quit()
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Reference
Find Interface
Find.Execute
Range Interface
Hyperlinks Object
As with any dataset you have to loop to hit all items in the data set to take action on specific in the dataset. You are not doing this in your code. In MSWord, you need to walk the document. For example I am show code for deletes, but this just as well could be your replace effort.
Example: VBA for just delete any hyper link
Sub RemoveHyperlinksInDoc()
' You need to delete collection members starting from the end going backwards
With ActiveDocument
For i = .Hyperlinks.Count To 1 Step -1
.Hyperlinks(i).Delete
Next
End With
End Sub
Example PowerShell for delete all hyperlinks
Param
(
[string]$Document = $Word.Documents.Open((Read-Host -Prompt 'Enter the full path to the Word document'))
)
$Word = New-Object -ComObject Word.application
$Hyperlinks = #($Document.Hyperlinks)
$hyperlinks | ForEach { $_.Delete() }
$document.save()
$document.Close()
$word.quit()
Example: PowerShell for delete only image hyperlinks
Param
(
[string]$Document = $Word.Documents.Open((Read-Host -Prompt 'Enter the full path to the Word document'))
)
$Word = New-Object -ComObject Word.application
$Hyperlinks = #($Document.Hyperlinks)
$Hyperlinks | ForEach {
If ($_.Shape) {$_.Delete()}
Else {$_.Name;Write-Warning -Message 'Hyperlink is not a graphic and will not be removed'}
}
$Document.save()
$Document.Close()
$Word.quit()
I need to run a powershell script with a parameter from a word macros. For now even the attempts to simply run a script from VBA are not sucessful. I do the following:
Sub Powershell_Run()
sCmd = "powershell -file ""C:\Users\i351063\Desktop\Scripts\NewAttempt.ps1"""
Set xShell = CreateObject("Wscript.Shell")
Set xShellExec = xShell.Run(sCmd)
rReturn = xShellExec.Run("C:\Users\i351063\Desktop\Scripts\NewAttempt.ps1")
End Sub
The execution of this code returns an error: "Run-time error '70': Permission denied" on the line Set xShellExec = xShell.Run(sCmd). What do I do wrong? How to fix the error?
Thanks a lot in advance!
UPD: Powershell code (I want to pass the filename as a parameter from VBA to PS, for now it's initialized right in the code)
Param([string]$filename)
$filename = "HT.docm"
Add-Type -AssemblyName "Microsoft.Office.Interop.Word"
$word = [Runtime.Interopservices.Marshal]::GetActiveObject('Word.Application')
$wshell = New-Object -ComObject Wscript.Shell
$doc = $word.Documents | Where-Object {$_.Name -eq "$filename"}
If (!$doc)
{
$form.Close()
[void] $wshell.Popup("Failed to find an opened report",0,"Report not found",0x1)
$word.ScreenUpdating = $true
Break
}
Else {...}
I am using the following Powershell script to run some macro's on worksheets within a workbook. What I want to do is open a workbook, run a certain macro on specific worksheets, and save the workbook to a different location. For testing purposes, I am just running it on all the worksheets.
Here is what I have so far:
$excel = New-Object -ComObject excel.application
$xlfileformat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
$workbook = $excel.Workbooks.Open($(join-path -path R:\TestOutput\Nick -childpath tmp.xlsm))
$worksheets = $Workbook.Worksheets | ForEach-Object {$_.name}
ForEach ($Worksheet in $Worksheets) {
$WS = $Workbook.Worksheets.Item($Worksheet)
$excel.Run("RunMacro")
$workbook.save()
} #End ForEach
$workbook.saveas($(join-path -path "R:\TestOutput\Nick" -childpath "Test_Output-post"), $xlfileformat)
$excel.quit()
With this script I would expect the macro to run on all the worksheets within the workbook, however, only the last worksheet has the macro applied to it. At first I didn't have the $workbook.save() in there. I added it because I though it was reinitialize the workbook every time it loaded a new worksheet, therefor losing the changes from the last run. This didn't fix the issue though.
I know I could modify the VBA script itself, but I want to eventually turn this into a function with the macro and worksheet(s) as variable inputs.
As I was typing this out, I found the answer. The problem was, I never activated the worksheet I selected. The following code fixed the problem:
$excel = New-Object -ComObject excel.application
$excel.DisplayAlerts = $false
$xlfileformat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
$workbook = $excel.Workbooks.Open($(join-path -path R:\TestOutput\Nick -childpath tmp.xlsm))
$worksheets = $Workbook.Worksheets | ForEach-Object {$_.name}
ForEach ($Worksheet in $Worksheets) {
$WS = $Workbook.Worksheets.Item($Worksheet)
$WS.Activate()
$excel.Run("RunMacro")
} #End ForEach
$workbook.saveas($(join-path -path "R:\TestOutput\Nick" -childpath "Test_Output-post"), $xlfileformat)
$excel.quit()
The fix was adding the $WS.Activate() command in the loop.