Excel macro saving sheet as pdf to sharepoint to location dependent on input in new folder - excel

I am new to VBA and have to complete a task for my manager about saving sheet as a pdf into sharepoint (if firstly can be done) which creates a folder at the location with the same name as the saved PDF and saves the pdf there.
Now where it gets harder for me is at the location in sharepoint there are 3 folders, for USD, EUR and GBP and depending on a field in the excel (which will denote one of the 3 currencies) it will have to be saved at that location
(Sharepoint URL or mapped to network drive)\Quote\USD\new folder created with file name matching pdf\pdf file
(Sharepoint URL or mapped to network drive)\Quote\EUR\new folder created with file name matching pdf\pdf file
(Sharepoint URL or mapped to network drive)\Quote\GBP\new folder created with file name matching pdf\pdf file
Is it the sharepoint URL will work or is it only when mapped to the network drive (which I have with a filepath with my username which im guessing would stop working from anyone else but me)
I am using the below which saves to sharepoint but with me as User_1 I cant see how anyone else will be able to?
Sub test()
ChDir "C:\Users\user_1\company\Sales Team - Documents\Quotes"
With Sheets("Quote")
.ExportAsFixedFormat _
Type:=xlTypePDF, _
Filename:="C:\Users\user_1\company\Sales Team - Documents\Quotes\" & ActiveSheet.Range("B2").Value & " Quote.pdf", _
Quality:=xlQualityStandard, _
IncludeDocProperties:=True, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=False
End With
end sub
It took me a while to figure out the above (which im sure is really simple) but i am still learning,
Can anyone help me with this?
Thanks

OK, I probably overshot the targer a bit...
This code only works with drives mapped to the normal Filesystem, if you want to use a network path you can read some more on the topic here: Cannot save file to Sharepoint Online using VBA (Permissions error)
Unfortunately I do not have a way to test code against a Sharepoint Server until I get back to work.
First of, you need to add the Microsoft Scripting Runtime to your project, described here: https://stackoverflow.com/a/3236348
You can call the publishQuoteToDirectory sub from anywhere inside your project. I would recommend a custom Ribbon in the Application that passes the activeSheet Object, but you could also just point a Makro to runExportExample and fill in some static parameters.
sheetToPublish: Expects a Worksheet Object, you can use ActiveSheet if you want
publishingPath: The "Quotes" Folder
currencyCell: The Cell which holds the Currency
fileName: If you want to override the Filename for some reason
The Select Case structure decides which currency the Worksheet Contains, it also accepts the signs of the currencies, can be extended with whatever you want.
quoteNamePathPart I was not exactly sure how you meant this in your main question, this gives you the option to use the Workbook or the Worksheet Name, choose whichever you want.
The FileSystemObject helps us with building a valid path, there are other methodes to create this but I prefer using it over them because it gives direct access to the Microsoft Filesystem.
BuildFullPath is a separate sub because it has to call itself recursively. The FSO can not create nested Folder in one Action. An alternative would be to use the Shell (described here: https://stackoverflow.com/a/4407468).
This is the whole Magic, if you have any Question regarding the code feel free to ask.
There are definitely other easier, faster, more secure ways to solve this. My knowledge with VBA is still limited and I don't know all the best practices, but the code should get the job done. (#all the other, feel free to criticize)
Code:
'all this sits in a standart module:
Option Explicit
Private Const StandartCurrencyCell As String = "B2"
Private Const StandartFileName As String = "Quote.pdf"
Public Sub runExportExample()
publishQuoteToDirectory _
sheetToPublish:=ActiveSheet, _
publishingPath:="C:\Users\User1\company\Sales Team - Documents\Quotes\", _
currencyCell:="B2", _
fileName:="SomeOtherFileName.pdf"
End Sub
Public Sub publishQuoteToDirectory(sheetToPublish As Worksheet, Optional publishingPath As String, Optional currencyCell As String, Optional fileName As String)
'Sanitize the input if necessary
If publishingPath = "" Then publishingPath = Environ$("USERPROFILE") & "\Quotes\"
If currencyCell = "" Then currencyCell = StandartCurrencyCell
If fileName = "" Then fileName = StandartFileName
Dim currencyPathPart As String
Select Case sheetToPublish.Range(currencyCell).Value2
Case "USD", "$"
currencyPathPart = "USD"
Case "EUR", "€"
currencyPathPart = "EUR"
Case "GBP", "£"
currencyPathPart = "GBP"
Case Else
currencyPathPart = "OtherCurrencies"
End Select
Dim quoteNamePathPart
With New FileSystemObject
'I'm a bit sceptic on the correctness of this, since your PDF is called "Quote" the FOlder Name would be "Quote" as well
'Comment out whatever you don't want
'I think this should be:
quoteNamePathPart = .GetBaseName(sheetToPublish.Parent.Name) 'this will use the Workbook Name (without Suffix)
'not:
'quoteNamePathPart = sheetToPublish.Name 'This will use the Name of the Sheet
'build the path and create folder, using the FSO takes care of missing Seperators etc.
publishingPath = .BuildPath(publishingPath, currencyPathPart)
publishingPath = .BuildPath(publishingPath, quoteNamePathPart)
BuildFullPath (publishingPath)
publishingPath = .BuildPath(publishingPath, fileName)
End With
On Error GoTo ExportFailed
sheetToPublish.ExportAsFixedFormat _
Type:=xlTypePDF, _
fileName:=publishingPath, _
Quality:=xlQualityStandard, _
IncludeDocProperties:=True, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=False
Exit Sub
ExportFailed:
MsgBox prompt:="The Export of the File: " & fileName & " failed" & vbCrLf & "The expected Output Path was: " & publishingPath, Title:="Export Failed"
End Sub
Sub BuildFullPath(ByVal FullPath)
'FSO can only create one Folder at a time, so I used a recursive function found here: https://stackoverflow.com/a/4407468
Dim fso As New FileSystemObject
If Not fso.FolderExists(FullPath) Then
BuildFullPath fso.GetParentFolderName(FullPath)
fso.CreateFolder FullPath
End If
End Sub

Related

Deploying Macros as Add Ins with Custom Ribbon Buttons to the entire office

I have been searching for a way to distribute macros to my tech illiterate office in the simplest way I can.
From my research, saving the Macros into a .xlam add-in would seem to be headed in the right direction.
Is it also possible to set up a custom ribbon tab in this manner?
Thus far I have failed to find any guides and our office security may also block certain avenues.
Edit:
Using W-Hit's excellent solution, and setting up the folder structure as indicated, it definitely helps make deploying an update much easier with the DeployAddIn subroutine.
I also found it useful to put the DeployAddIn and InstallAddin subroutines into a custom ribbon tab of their own!
I ran into a problem with the InstallAddin subroutine however: how to format the XML text in VBA without running into syntax errors.
I discovered that Each Element must have mso at the start e.g. <button> becomes <mso:button> and each "speech marked section" in a line must have ""double speech marks".
Perhaps the easiest way to use this install function is to save and edit the code into an active file, then open C:\Users[username]\AppData\Local\Microsoft\Office\Excel.officeUI in Notepad++. Then simply perform a find and replace to add in the extra quotation marks and paste it into the ribbonXML = "insert your text here" section of the code, ensuring it is encapsulated by the final speech marks to mark the entire section as a text string.
I might also look into adding extra functionality here... having an inputbox or userform that allows you to paste the code in at this point rather than have you enter the VBA editor to paste it in.
I currently do this, and it's a somewhat in depth process to setup, but runs smoothly once it is.
1st step is to create a folder structure with testing and production copies of your .xlam files that you are the admin for.
2nd, in the production folder, right click all .xlam files and set the attributes in the properties to Read-only. If you don't you'll never be able to update the addin if anyone else is in it.
3rd, when you make updates to the code in the testing file, just replace the production file with the updated file, and change to Read-only again. Users will only have to close all instances of excel and reopen to have the most up-to-date copy of the add-in.
Below is an admin add-in I use to move testing files to production.
Sub DeployAddIn()
'Author : Ken Puls (www.excelguru.ca)
'Macro Purpose: To deploy finished/updated add-in to a network
' location as a read only file
Dim strAddinDevelopmentPath As String
Dim strAddinPublicPath As String
Dim FSO As New FileSystemObject
'Set development path
ChDrive "R:"
ChDir "R:\addins\PROJECTS"
strAddinDevelopmentPath = Application.GetOpenFilename()
If strAddinDevelopmentPath = "False" Then
Exit Sub
ElseIf InStr(strAddinDevelopmentPath, "\PRODUCTION\") > 1 Then
If MsgBox("You've Selected a Production File To Replace a Production File. Would You Like To Continue Anyway?", vbYesNo) = vbNo Then
Exit Sub
End If
End If
'Get Desitination path
strAddinPublicPath = Replace(strAddinDevelopmentPath, "TESTING", "PRODUCTION")
'Create dir if it doesn't exist
On Error Resume Next
MkDir Left(strAddinPublicPath, InStrRev(strAddinPublicPath, "\") - 1)
On Error GoTo 0
'Turn off alert regarding overwriting existing files
Application.DisplayAlerts = False
'overwrite existing file
On Error Resume Next
SetAttr strAddinPublicPath, vbNormal
On Error GoTo 0
FSO.CopyFile strAddinDevelopmentPath, strAddinPublicPath, True
SetAttr strAddinPublicPath, vbReadOnly
'Resume alerts
Application.DisplayAlerts = True
End Sub
4th, I've also written a macro to change the custom ribbon. The below link, in addition Ron deBruin's site is useful. https://grishagin.com/vba/2017/01/11/automatic-excel-addin-installation.html
Code to automate addin install after you get the right text from the officeUI file
Sub InstallAddin()
'Adapted from https://grishagin.com/vba/2017/01/11/automatic-excel-addin-installation.html
Dim eai As Excel.AddIn
Dim alreadyinstalled As Boolean
Dim ribbonXML As String
'check if already installed
For Each eai In Application.AddIns
If eai.Name = "Main addin.xlam" Then
eai.Installed = False
Exit For
End If
Next
'add and install the addin
Set eai = Application.AddIns.Add("path to Main addin.xlam", False)
eai.Installed = True
'append quick access ribbon xml to add button
ClearCustRibbon
LoadNewRibbon
'have to close addin for it to load properly the first time
Workbooks("Main addin.xlam").Close
End Sub
Sub ClearCustRibbon()
'https://social.msdn.microsoft.com/Forums/vstudio/en-US/abddbdc1-7a24-4664-a6ff-170d787baa5b/qat-changes-lost-when-using-xml-to-modify-ribbon-excel-2016-2016?forum=exceldev
Dim hFile As Long
Dim ribbonXMLString As String
hFile = FreeFile
OfficeUIFilePath = Environ("USERPROFILE") & "\AppData\Local\Microsoft\Office\Excel.officeUI"
ribbonXMLString = "<mso:customUI xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui"">" & _
"<mso:ribbon>" & _
"<mso:qat>" & _
"<mso:sharedControls>" & _
"</mso:sharedControls>" & _
"</mso:qat>" & _
"</mso:ribbon>" & _
"</mso:customUI>"
Open OfficeUIFilePath For Output Access Write As hFile
Print #hFile, ribbonXMLString
Close hFile
End Sub
Sub LoadNewRibbon()
Dim hFile As Long
hFile = FreeFile
OfficeUIFilePath = Environ("USERPROFILE") & "\AppData\Local\Microsoft\Office\Excel.officeUI"
ribbonXML = "your ribbon text here"
Open OfficeUIFilePath For Output Access Write As hFile
Print #hFile, ribbonXML
Close hFile
End Sub
***IMPORTANT---- If you install the addin manually, make sure you select no when prompted if you want the file saved to the local machine. If you save it to the local machine, it creates a local copy, and will never update if you make changes to the network copy or need to fix an error.
There are more tips, but you'll mostly need to adapt them to fit how you operate. Hope that helps.

Define varables in Excel then use in Access VBA at same time

Is it possible to define a string variable in excel and then use that variable inside Access?
I have a program where in Excel a window pops up asking for where a file is located which will feed into an Access database - get processed - then shoot into Excel. The problem is that this is for a lot of different people and so each computer is going to have its own extension address of where the file is located, so it is necessary to have it be easy for users to identify where their file is located instead of hard-coding it into the VBA.
No matter what I try, I can't seem to figure out how to get the string variable to talk to the access database so it knows where to go look for the file.
I can't seem to find a solution for this. Anyone have any ideas?
Here is the code I have so far: This is what is inside the excel file----
'CommandButton1 is a button inside of a form window that pops up for the user to enter the address of the file
Public Sub CommandButton1_Click()
'both of these are public/global variables defined in a global macro
locationaddress = txbBrowse2.Value
LocationOfData = txbBrowse.Value
Dim appAccess As Object
Set appAccess = CreateObject("Access.Application")
'location of data is the location of the access file itself
location address is the string that I'm trying to feed into access
appAccess.OpenCurrentDatabase LocationOfData, exclusive:=False
appAccess.Application.Run "DoExcelImport"
End Sub
'Here is the code inside the access file, the idea is that it will modify the "Import-TEST" saved import. It will change where it pulls the excel sheet that contains a bunch of items that requires access to process.
Sub DoExcelImport()
DoCmd.SetWarnings False
Dim ies As ImportExportSpecification, i As Long, oldXML() As String, newXML As String
Set ies = CurrentProject.ImportExportSpecifications("Import-TEST")
oldXML = Split(ies.XML, vbCrLf, -1, vbBinaryCompare)
newXML = ""
For i = 0 To UBound(oldXML)
If i = 1 Then
' re-write the second line of the existing XML
newXML = newXML & _
"<ImportExportSpecification Path = """ & _
locationaddress & _
""" xmlns=""urn:www.microsoft.com/office/access/imexspec"">" & _
vbCrLf
Else
newXML = newXML & oldXML(i) & vbCrLf
End If
Next
ies.XML = newXML
ies.Execute
Set ies = Nothing
DoCmd.SetWarnings True
End Sub
Probably the easiest way might be using
SaveSettings(AppName As String, Section As String,Key As String, Setting As `String)
to store the string in the registry,
GetSettings((AppName As String, Section As String,Key As String)
to get it in Access, and
DeleteSetting (AppName as String)
to delete it.
Is probably a bit abusing the registry, but an easy way.

Fixed save as filename in PDF does not work anymore Office 2016

I recently updated to office 2016 and now my macro that i am using to select a range in excel, and then convert this range to PDF and automatically send an email, does not fully work.
Before when i used this macro, the filename was automatically filled in the SaveAs dialog box, but now it is empty. I do not understand why.
Does anyone else has a problem like this or know how to fix it?
Here is my code:
Function Skicka_projektunderlag_PDF(Myvar As Object, FixedFilePathName As String, _
OverwriteIfFileExist As Boolean, OpenPDFAfterPublish As Boolean) As String
Dim FileFormatstr As String
Dim Fname As Variant
Dim ws As Worksheet
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws = Sheets("Partner_information")
Set ws1 = Sheets("Kundinformation")
Set ws2 = Sheets("Kalkyl")
If Dir(Environ("commonprogramfiles") & "\Microsoft Shared\OFFICE" _
& Format(Val(Application.Version), "00") & "\EXP_PDF.DLL") <> "" Then
If FixedFilePathName = "" Then
FileFormatstr = "PDF Files (*.pdf), *.pdf"
Fname = Application.GetSaveAsFilename(ws.Range("B1").Value & " - Projektunderlag " & ws2.Range("BF104").Value & " " & ws1.Range("B3").Value _
, FileFilter:=FileFormatstr, Title:="Create PDF")
If Fname = False Then Exit Function
Else
Fname = FixedFilePathName
End If
If OverwriteIfFileExist = False Then
If Dir(Fname) <> "" Then Exit Function
End If
On Error Resume Next
Myvar.ExportAsFixedFormat _
Type:=xlTypePDF, _
FileName:=Fname, _
Quality:=xlQualityStandard, _
IncludeDocProperties:=True, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=OpenPDFAfterPublish
On Error GoTo 0
If Dir(Fname) <> "" Then Skicka_projektunderlag_PDF = Fname
End If
End Function
Best regards
AgatonSaxx
The following answer isn't refined but I have also been struggling with this problem in Word 2016 VBA to generate a default file name when Save As is selected in Word 2016
and wanted to share what I've found thus far as it is working with some success.
I was able to get the code semi-working again by adding an event handler.
Application.DocumentBeforeSave Event
example here https://msdn.microsoft.com/en-us/library/office/ff838299.aspx
tied to Using Events with Application Object
example here https://msdn.microsoft.com/en-us/library/office/ff821218.aspx
I moved my actual code to within the class module
Cancel=true
had to be added to the end of the code or the Save As dialog box would open twice.
This "solution" has some drawbacks that it only works once per document. So, if for some reason, you want to use SaveAs on the same document more than once, the name won't default. It also seems a bit clunky/limited for my taste but it is a start.
This "solution" is Word based but you should be able to do/ find something similar for Excel.
Hope this helps put you on the path to success. Apologies for not being a perfect answer. Just wanted to share lessons learned as maybe it will cut down on your time to a solution!

How to save a sheet array to PDF with a specific sheet order

I have a Workbook with multiple sheets which I want to select and convert to a single PDF file.
I have written the following code which works fine and creates the file:
Sub Print_Project_Report_To_PDF
Dim FilePathandName As String
MyDate = Format(DateSerial(Year(Now), Month(Now) - 1, 1), "mmmm yyyy")
MyPath = ThisWorkbook.Path & "\"
MyFile = "Project Progress Report - " & MyDate & ".pdf"
FilePathandName = MyPath & MyFile
ThisWorkbook.Sheets(Array("PR_COVER_PAGE", "PR_SUMMARY", _
"PR_PROJECT_DETAILS", "PR_INTERNAL RESOURCES", "PR_TIME", _
"PR_REVENUE_FORECAST_SUMMARY", "PR_ORIGINAL_REVENUE_FORECAST", _
"PR_ACTUAL_REVENUE_FORECAST", "PR_COSTS", "PR_ISSUES", "MONTHLY FINANCIAL REPORT", _
"PG-SC_COVER_LETTER", "PG-SC_CLAIM_SUMMARY", "PG-SC_TRADE", "PG-SC_HYDRAULICS", _
"PG-SC_MECHANICAL", "PG-SC_MEDICAL_GASES", "PG-SC_ELECTRICAL", "PG-SC_VARIATION", _
"PG-SC_MONTHLY_CASHFLOW", "PG-MH_COVER_LETTER", "PG-MH_CLAIM_SUMMARY", _
"PG-MH_TRADE", "PG-MH_HYDRAULICS", "PG-MH_MECHANICAL", "PG-MH_MEDICAL_GASES", _
"PG-MH_ELECTRICAL", "PG-MH_VARIATION", "PG-MH_MONTHLY_CASHFLOW", "CLIENT_COVER", _
"CLIENT_SUMMARY", "CLIENT_ISSUES")).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=FilePathandName, _
Quality:=xlQualityStandard, IncludeDocProperties:=True, _
IgnorePrintAreas:=False, OpenAfterPublish:=True
ThisWorkbook.Sheets("Dashboard").Select
End Sub
The problem is that the PDF file is not created with the sheets in order which I have specified in the array. They are in the order which they appear in the Workbook (Left to right). It correctly only includes the sheets I want but not in the order i want.
I dont want to change the order of the sheets in the Workbook either because it is setup in a specific, progressive way.
Can anyone help me with code which will allow me to be specific with the order of the sheets when the document is published?
Any help would be greatly appreciated.
I agree with #SiddharthRout in first idea/comment below the question. However, in quite similar situation when I print complicated PowerPoint presentation I use
PDFCreator application
At the first step I run that software and set 'stop printing' option. Than you could send to that software (in the way you print worksheet) each worksheet separately which would be separate document stacked in the list in the right order at the beginning. Using special feature you can match them into one document then and print it. It's very useful and quite reliable solution.
Here is some sample VBA code how copy the current workbook into a temp file and reorder a list of sheets. Use such a routine before printing:
Sub CopyAndReorder()
Dim wbCopy As Workbook
ThisWorkbook.SaveCopyAs "C:\TEMP\XXXX.XLS"
Set wbCopy = Workbooks.Open("C:\TEMP\XXXX.XLS")
ReorderSheets wbCopy
End Sub
Sub ReorderSheets(wb As Workbook)
Dim shNames As Variant, shName As Variant, sh As Worksheet
shNames = Array("Table3", "Table2", "Table1")
For Each shName In shNames
Set sh = wb.Sheets(shName)
sh.Move After:=wb.Sheets(wb.Sheets.Count)
Next
End Sub
(You have to adapt this code snippet to your needs, of course, use a better temp file name, delete the new file afterwards, provide the list of sheets from outside, etc, but I think you get the idea).

Automatically name a file based on cell data when saving a spreadsheet?

I have an .xltm template spreadsheet that I'm wondering if I can get a macro to populate the "save as" file name based on cell data, is this possible?
There are over 50 people who will have this spreadsheet, it's more of a form, and we are trying to figure out a way to keep the filenames uniform. I know there is the ThisWorkbook.BeforeSave, but I'm not really having any luck there. I just need it to make a file named something like $A$1 R $B$1 T $B$3.xlsx
Any ideas on how to do this?
Sure.
Sub SaveMyWorkbook()
Dim strPath As String
Dim strFolderPath as String
strFolderPath = "C:\"
strPath = strFolderPath & _
Sheet1.Range("A1").Value & "R" & _
Sheet1.Range("B1").Value & "T" & _
Sheet1.Range("B3").Value & ".xlsx"
ActiveWorkbook.SaveAs Filename:=strPath
End Sub
EDIT: After you clarified your question in your comment below, I can now safely say that the answer is: No, what you are asking is not possible.
What is possible is to put a big, fat command button on your sheet that says "Press me to save", and have that button call the above Sub. You can set a fixed folder, as in the example above, or have the user pick a folder using the FileDialog object (or the GetSaveAsFilename function, but then the user will be able to change the suggested filename, so less safe).

Resources