How to Print to PDF, not Save?" - vba

App Versions:
Outlook: Microsoft 365 Apps for enterprise
Adobe Acrobat Pro DC: version 2022.001.20117
DISCLAIMER:
My company has disabled "save as" functionality in Outlook.
Please do not suggest any VBA methods that involve ".SaveAs".
Question:
I want to print an Outlook email to PDF using the Adobe PDF (driver?) printer:
The basic process flow I want to automate:
I will open/select an email that I want to print to PDF
I will ctrl+P to print and then select the Adobe PDF as the printer
A "Save PDF File As" dialogue box appears
Within the dialogue, set the save location and set the filename and submit
I coded steps 1-3. Step 4 is where my problems begin.
I have not found a way to simulate the dialogue box process.
I tried AppActivate & SendKeys: the code runs but then it shifts the focus back to the VBE and therefore doesn't do what I need within the print dialogue.
I tried finding VBA code that replicates the backend process of the dialogue. I think the dialogue is a function of Adobe so finding VBA to talk to the process is hard.
I am trying with step 4 to set the save location using a variable then set the filename field using a variable then click save to finish the print process.
Dialogue box and relevant fields:
Note: I save attachments using .SaveAsFile as MailItem.SaveAs does not work.
Skip to the code heading titled, "Print/save email as PDF" to get to the good stuff:
Sub saveEmail()
'================================================================================
' Initialize variables
'================================================================================
Dim olSelection As Outlook.selection
Dim myItem As Outlook.MailItem
Dim olAtt As Outlook.Attachment
Dim olTempFolder As String
Dim myDate As String: myDate = Year(Now) & Month(Now) & Day(Now) & _
Hour(Now) & Minute(Now) & Second(Now)
Dim myPrinter As String
' Assign PDF printer to variable
myPrinter = "Adobe PDF"
' Assign the window title of the save as pdf dialogue
myDialogueTitle = "Save PDF File As"
'================================================================================
' Create email download path
'================================================================================
' Get the local temp folder path
tempPath = ""
tempPath = VBA.Environ("temp")
' Add Outlook Attachments subfolder to temp path
olTempFolder = tempPath & "\Outlook Attachments"
Debug.Print olTempFolder ' Print the folder path to immediate window
' If the path exists, check to make sure path is a directory, else create
dirExists = Dir(olTempFolder, vbDirectory)
If dirExists <> "" Then
dirAttr = GetAttr(olTempFolder)
' Check if path is directory (attribute "16")
If dirAttr <> 16 Then
MsgBox "There is an error with the specified path. Check code " & _
"try again."
End If
Else
' If folder does not exist, create
MkDir (olTempFolder)
End If
'================================================================================
' Create unique folder for this run
'================================================================================
olTempFolder = olTempFolder & "\emailToPDF-" & myDate
MkDir (olTempFolder)
'================================================================================
' Save attachments from selected email
'================================================================================
Set olSelection = ActiveExplorer.selection
Set myItem = olSelection.Item(1)
For Each olAtt In myItem.Attachments
attFullPath = olTempFolder & "\" & olAtt.DisplayName
olAtt.SaveAsFile (attFullPath)
Next
'===============================================================================
' Print/save email as
'================================================================================
' Set the default printer
Set mynetwork = CreateObject("WScript.network")
mynetwork.setdefaultprinter myPrinter
' Print the email
myItem.PrintOut
' Send keystrokes to Save As dialogue
AppActivate myDialogueTitle ' Activate the printer dialogue window
SendKeys myDate, True ' Change file name to be saved
SendKeys "{F4}", True ' Activate path text box
SendKeys "^a", True ' Select all contents of path text box
SendKeys "{DEL}", True ' Delete selected contents of text box
SendKeys olTempFolder, True ' Set desired save path in the path location box
SendKeys "{ENTER}", True ' Press enter to set the path
SendKeys "{ENTER}", True ' Press enter to submit/save as
'================================================================================
'
'================================================================================
End Sub
Again, please do not suggest a solution involving the ".SaveAs" method. Our IT administrators disabled this functionality in Outlook so VBA code calling it does not work.

In the code I didn't find a place where Outlook objects declared at the beginning of the functions are declared.
Dim olSelection As Outlook.selection
Dim myItem As Outlook.MailItem
Dim olAtt As Outlook.Attachment
It seems you need to get the currently selected item in the Explorer window. Use the Selection property of the Explorer class which returns a Selection object that contains the item or items that are selected in the explorer window.
Set myOlExp = Application.ActiveExplorer
Set myOlSel = myOlExp.Selection
For x = 1 To myOlSel.Count
If myOlSel.Item(x).Class = OlObjectClass.olMail Then
Then you can deal with a selected item. The Word object model can be used for dealing with message bodies. See Chapter 17: Working with Item Bodies for more information. So, you are free to use the Document.SaveAs2 method which saves the specified document with a new name or format. Some of the arguments for this method correspond to the options in the Save As dialog box (File tab).
Also you may find the Document.ExportAsFixedFormat2 method which saves a document as PDF or XPS format.

In order to handle the Print to pdf dialog proceed in the next way:
Copy the next API functions declaration on top of the module (in the declarations area):
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias _
"FindWindowExA" (ByVal hwnd1 As LongPtr, _
ByVal hwnd2 As LongPtr, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As LongPtr
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, _
ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
There are declarations for 64 bit systems (VBA 7). It can be adjusted to work for both cases.
Use this way to deal with the dialog handlers, to change the pdf file name and press Save:
Sub handlePrintToPDF()
Dim pdfHwnd As LongPtr, hwnd1 As LongPtr, hwnd2 As LongPtr, hwnd3 As LongPtr
Dim hwndCombo As LongPtr, hwndEdit As LongPtr, hwndSave As LongPtr
Dim tempPath, olTempFolder As String, myDate As String
tempPath = VBA.Environ("temp")
olTempFolder = tempPath & "\Outlook Attachments"
myDate = Year(Now) & Month(Now) & Day(Now) & Hour(Now) & Minute(Now) & Second(Now) & ".pdf"
Do While pdfHwnd = 0
DoEvents
pdfHwnd = FindWindow("#32770", "Save PDF File As"): Debug.Print Hex(pdfHwnd)
Loop
hwnd1 = FindWindowEx(pdfHwnd, 0, "DUIViewWndClassName", vbNullString): Debug.Print Hex(hwnd1)
hwnd2 = FindWindowEx(hwnd1, 0, "DirectUIHWND", vbNullString): Debug.Print Hex(hwnd2)
hwnd3 = FindWindowEx(hwnd2, 0, "FloatNotifySink", vbNullString): Debug.Print Hex(hwnd3)
hwndCombo = FindWindowEx(hwnd3, 0, "ComboBox", vbNullString): Debug.Print Hex(hwndCombo)
hwndEdit = FindWindowEx(hwndCombo, 0, "Edit", vbNullString): Debug.Print Hex(hwndEdit)
Const WM_SETTEXT = &HC
Dim pdfFileFullName: pdfFileFullName = olTempFolder & "\" & myDate
SendMessage hwndEdit, WM_SETTEXT, 0&, ByVal "MyMail pdf" 'use here what you need as pdf docment to be saved name
hwndSave = FindWindowEx(pdfHwnd, 0, vbNullString, "&Save"): Debug.Print Hex(hwndSave)
Const WM_LBUTTON_DOWN = &H201, BM_CLICK = &HF5
SendMessage hwndSave, WM_LBUTTON_DOWN, 0&, 0&
SendMessage hwndSave, BM_CLICK, 0, ByVal 0&
End Sub
Call the above sub after myItem.PrintOut:
handlePrintToPDF
But launching the print window (from Outlook) will block VBA. I used it as an automation from Excel...

Related

Implementing a File Picker dialog box in Outlook VBA

So you're creating an Outlook macro that prompts the users to select file(s) - but you can't quite get it right. Hopefully this will help.
There seems to be a number of related questions, but I'm consolidating everything here and showing what worked for me in the end.
Outlook Application.FileDialog not found
Where is Outlook's save FileDialog?
How to implement Application.FileDialog using Outlook VBA?
FilePicker in Macro opens dialogbox in background
The most annoying thing for me was the fact that once you've implemented a workaround, the File Dialog will open in the background whenever you're not running the code from VBE directly.
Right out of the gate, the Outlook Application doesn't support VBA FileDialog object. Theoretically Outlook itself supports this since you can do File > Save As and File > Open & Export...but you can't simply call the object from VBA.
For my project - I have a sub that replaces tokens with user input, but I wanted to give folks the option of picking which Template to open. I'd recommend reading up on the FileDialog object itself as there are several helpful examples in the Microsoft documentation.
There are a number of options, but below are the 2 main workarounds I've found. I prefer the first method as it doesn't require adding a reference - meaning that the macro(s) can be more easily shared without compilation errors.
Method 1: No References Needed (hopefully)
#If VBA7 Then
Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal win As LongPtr) As LongPtr
#Else
Private Declare Function SetForegroundWindow Lib "user32" (ByVal win As Long) As Long
#End If
Option Explicit
Sub CreateEmailUsingSelectedTemplate()
Dim xlApp As Object
Set xlApp = CreateObject("Excel.Application")
'MsgBox "The top-level window handle is: " & xlApp.hWnd
Dim fd As Office.FileDialog
Set fd = xlApp.FileDialog(msoFileDialogFilePicker)
Dim vrtSelectedItem As Variant
SetForegroundWindow (xlApp.hWnd)
With fd
.InitialFileName = Environ("APPDATA") & "\Microsoft\Templates\"
.Filters.Add "All Files", "*.*", 1
.Filters.Add "Templates", "*.oft", 2
.FilterIndex = 2
If .Show = -1 Then
For Each vrtSelectedItem In .SelectedItems
MsgBox "Selected item's path: " & vrtSelectedItem
'FindAndReplaceTokens CStr(vrtSelectedItem)
Next vrtSelectedItem
Else 'If the user presses Cancel...
MsgBox "Hit cancel instead of Accept"
Exit Sub
End If
End With
End Sub
Method 2: Early Binding
See FilePicker in Macro opens dialogbox in background and File dialog box not displaying on top and not visable
'Set reference to 'Microsoft Excel XX Object Library' in
'Tools > References
#If VBA7 Then
Private Declare PtrSafe Function FindWindowA Lib "user32" (ByVal class As String, ByVal caption As String) As LongPtr
Private Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal win As LongPtr) As LongPtr
#Else
Private Declare Function FindWindowA Lib "user32" (ByVal class As String, ByVal caption As String) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal win As Long) As Long
#End If
Option Explicit
Sub ShowDialogBox()
Dim fd As Office.FileDialog
Dim xlApp As Excel.Application
Dim hxl As LongPtr
Dim vrtSelectedItem As Variant
Set xlApp = New Excel.Application
Set fd = xlApp.FileDialog(msoFileDialogFilePicker)
hxl = FindWindowA("XLMAIN", "EXCEL")
If Not IsNull(hxl) Then
SetForegroundWindow (hxl)
End If
If fd.Show = -1 Then
For Each vrtSelectedItem In fd.SelectedItems
MsgBox "Selected item's path: " & vrtSelectedItem
'Put your code here
Next vrtSelectedItem
Else
MsgBox "User hit cancel"
Exit Sub
'Do something different here
End If
End Sub

VBA to click IE11 Save As dialogue in Win7x64

I'm trying to get VBA to automate saving a file from IE. Thanks to various posts on these forums, I can login, navigate pages, and click the download link. The Save prompt appears at the bottom of IE, then I'm stuck:
I've been trying to use the code samples from https://www.mrexcel.com/forum/excel-questions/502298-need-help-regarding-ie-automation-using-vba-post3272730.html#post3272730, but the second FindWindow always returns 0:
hWnd = FindWindowEx(hWnd, 0, "DUIViewWndClassName", vbNullString)
I'm using VBA 7.0 in Excel 14, and IE11.
There is advice at the top of the original post:
'Note - IE may block the download, displaying its Information Bar at
the top of the tab, and preventing this program from 'automatically
downloading the file. To prevent this, add NRLDC to IE's Trusted
sites (Tools - Internet Options - 'Security - Trusted sites - Sites)
I can't access the trusted sites list due to IT policy, but the download prompt appears, so I don't think this is the issue.
The code I've taken is from Doongie's reply, which indicates it's updated for Windows 7:
Private Sub File_Download_Click_Save()
Dim hWnd As Long
Dim timeout As Date
Debug.Print "File_Download_Click_Save"
'Find the File Download window, waiting a maximum of 30 seconds for it to appear
timeout = Now + TimeValue("00:00:30")
Do
hWnd = FindWindow("#32770", "") 'returns various numbers on different runs: 20001h 10440h
DoEvents
Sleep 200
Loop Until hWnd Or Now > timeout
Debug.Print " File Download window "; Hex(hWnd)
If hWnd Then
SetForegroundWindow hWnd
'Find the child DUIViewWndClassName window
hWnd = FindWindowEx(hWnd, 0, "DUIViewWndClassName", vbNullString) 'always returns 0
Debug.Print " DUIViewWndClassName "; Hex(hWnd)
End If
If hWnd Then
'Find the child DirectUIHWND window
hWnd = FindWindowEx(hWnd, 0, "DirectUIHWND", "")
Debug.Print " DirectUIHWND "; Hex(hWnd)
End If
If hWnd Then
'Find the child FloatNotifySink window
hWnd = FindWindowEx(hWnd, 0, "FloatNotifySink", "")
Debug.Print " FloatNotifySink "; Hex(hWnd)
End If
If hWnd Then
'Find the child ComboBox window
hWnd = FindWindowEx(hWnd, 0, "ComboBox", "")
Debug.Print " ComboBox "; Hex(hWnd)
End If
If hWnd Then
SetForegroundWindow hWnd
'Find the child Edit window
hWnd = FindWindowEx(hWnd, 0, "Edit", "")
Debug.Print " Edit "; Hex(hWnd)
End If
If hWnd Then
'Click the Save button
SetForegroundWindow hWnd
Sleep 600 'this sleep is required and 600 milliseconds seems to be the minimum that works
SendMessage hWnd, BM_CLICK, 0, 0
End If
End Sub
Is there any way (that won't get me in trouble with IT!) that I can inspect the handle numbers of the IE elements? Code inspector only shows me the page code, not IE dialogues.
Is there a list of possible element names for lpsz1 defined somewhere, as they apply to elements of IE?
Public Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Have you tried the dreaded sendkeys?
Application.SendKeys "%{S}"
Application.SendKeys "%{O}"
In my IE Automation used below code to save file from IE. Below code requires VBA reference to UIAutomationCore.dll and can be found at
%windir%/sysWow64/UIAutomationCore.dll
and enable trust access to vba by
File -> Options -> Trust Center -> Trust Center Settings -> Macro Settings -> Check Trust access to the VBA
Private Sub InvokeSaveButton(IEHwnd As Long)
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Set o = New CUIAutomation
Dim h As Long
h = IEHwnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Save")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
End Sub
You could try the urlmon library. Change the url and file name + extension to what you need to.
It will probably not work on a website where you have to log in to get to the file.
Public Declare Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" (ByVal pCaller As Long, _
ByVal szURL As String, ByVal szFileName As String, ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
Public Function DownloadFile(URL As String, LocalFilename As String) As Boolean
Dim errValue As Long
errValue = URLDownloadToFile(0, URL, LocalFilename, 0, 0)
If errValue = 0 Then
MsgBox "Download Completed, saved at: " & LocalFilename
Else
MsgBox "There was an error downloading the file"
End If
End Function
Sub DoIt()
DownloadFile "http://www.blahblahblah.com/somefolder/somefiles.xlsx", "C:\Users\Public\Documents\SavedFile.xlsx"
End Sub

how to download a pdf.php file from web browser using excel vba

Hi I am trying to download a file named " view.pdf.php file from web browser and save it as pdf file.(file is obtained while clicking on print icon in web browser, it opens a .pdf.php file in another browser)
Tried some samples from stack over flow but unable to get that downloaded in correct format.
Code tried:
Declare Function URLDownloadToFile _
Lib "urlmon" Alias "URLDownloadToFileA" _
(ByVal pCaller As Long, ByVal szURL As String, _
ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Function DownloadFile(URL As String, LocalFileName As String) As Boolean
Dim lngRetVal As Long
lngRetVal = URLDownloadToFile(0, URL, LocalFileName, 0, 0)
If lngRetVal = 0 Then DownloadFile = True
End Function
Sub Test()
DownloadFile "https://app.goscripts.com/vendor/viewreferral.php?Id=XXXXX", "\\test\test.pdf"
End Sub
when trying to open the downloaded file it shows as " Adobe Acrobat Reader could not open'test.pdf' because it is either not a supported file type or because the file has been damaged"
Please help me in fixing this error.
Located an alternate method to save the file from web browser,
Code:
Const OLECMDEXECOPT_DODEFAULT = 0
Const OLECMDEXECOPT_PROMPTUSER = 1
Const OLECMDEXECOPT_DONTPROMPTUSER = 2
Const OLECMDEXECOPT_SHOWHELP = 3
Const OLECMDID_SAVEAS = 4
Const PRINT_WAITFORCOMPLETION = 2
Sub Sample()
Dim objIE, sh As Object
Set objIE = CreateObject("InternetExplorer.Application")
objIE.Navigate "https://123.com/vendor/vieworder.pdf.php?OrderId=XXXX"
objIE.Visible = True
Do While objIE.ReadyState <> 4
DoEvents
Loop
objIE.ExecWB OLECMDID_SAVEAS, OLECMDEXECOPT_DODEFAULT
Set sh = CreateObject("WScript.Shell")
sh.AppActivate "Save AS"
Application.Wait (Now + TimeValue("00:00:02"))
sh.SendKeys "done", 1
sh.SendKeys ("{ENTER}")
objIE.Quit
End Sub
Thank for your support

Sendkey twice based on success?

I was using sendkey to access Power Query and connect to SharePoint Folder. Everything was smooth until the Power Query Data Preview Dialog appears.
How do I allow sendkey to continue after the dialog appears? I'm using button to start macro and using Excel 2016.
Option Explicit
Sub Button1_Click()
Dim spPath As String
Dim strkeys As String
spPath = "" 'SharePoint Link
strkeys = "%APNFO" & spPath & "{Enter}{TAB 4}{Enter}"
'stops at first{Enter}, {TAB 4}{Enter} for EDIT
Call SendKeys(strkeys)
End Sub
Update
Also tried to sendkey twice with True but same result, Stops at dialog.
Option Explicit
Sub Button1_Click()
Dim spPath As String
Dim strkeys As String
Dim strkeys2 As String
spPath = ""
strkeys = "%APNFO" & spPath & "{Enter}"
strkeys2 = "{TAB 4}{Enter}"
Call SendKeys(Trim(strkeys), True)
Call SendKeys(Trim(strkeys2), True)
Debug.Print strkeys2
End Sub
Update2
I tried what #peh suggested, using sleep() and Application.wait(). I found out that once the macro is initialized, sendkey1 started and stopped by the Application.wait(). Only after the waiting time ends, then sendkey1 is being processed. And once sendkey1 started, sendkey2 also starts.
Also tried adding DoEvents, sendkey1 works perfect. However only after clicking the Cancel button, Application.wait() and sendkey2 will start.
Call SendKeys(Trim(strkeys))
Debug.Print Now & "Send Key 1"
'Do Events
Application.wait (Now + TimeValue("0:00:10"))
Call SendKeys(Trim(strkeys2), True)
Debug.Print Now & "Send Key 2"
Pannel
If the dialogue box is the same every time, or contains a consistent string of text in the caption, you may be able to use it's caption to detect when it appears using this function in a loop with a timer that searches for a reasonable amount of time for the dialogue box:
Private Function GetHandleFromPartialCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim lhWndP As Long
Dim sStr As String
GetHandleFromPartialCaption = False
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
GetWindowText lhWndP, sStr, Len(sStr)
sStr = Left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 Then
GetHandleFromPartialCaption = True
lWnd = lhWndP
Exit Do
End If
lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function
Where sCaption is the name of your dialogue box. Then in your main body of code use:
If GetHandleFromPartialCaption(lhWndP, "Your Dialogue Box Caption") = True Then
SendKeys(....
I am on my linux box right now so I can't tinker with this to test, but you might attempt to read other properties of the window with a utility like:
https://autohotkey.com/boards/viewtopic.php?t=28220
Edit: if SendKeys absolutely won't work, and you don't want to go the UI automation route, and you don't mind a dependency, you could install AutoHotkey and script that from VBA (e.g. using the Shell() command). AHK is more robust when it comes to keyboard macro automation.
If you had a unique classname, for example, you could use FindWindowEx to get the window handle:
Module-scoped ~
#If VBA7 Then
'32-bit declare
Private Declare Function FindWindowEx Lib "USER32" _
Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
#Else
'64-bit declare
Private Declare PtrSafe Function FindWindowEx Lib "USER32" _
Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
#End If
Procedure ~
Dim appcaption as String
appcaption = "Excel"
#If VBA7 Then
Dim parenthandle as Long, childhandle as Long
#Else
Dim parenthandle as LongPtr, childhandle as LongPtr
#End If
parenthandle = FindWindow(vbNullString, appcaption)
If parenthandle Then
childhandle = GetWindow(parenthandle, GW_CHILD)1
Do Until Not childhandle
childhandle = GetWindow(childhandle, GW_HWNDNEXT)
Loop
End If
If childhandle Then
'
End If
This code is only proof of concept, as you could have muliple Excel Windows open, for example. It should give a good starting point, however.

How can I create text files with special characters in their filenames

Demonstration of my problem
Open a new Excel workbook and save these symbols 設計師協會 to cell [A1]
insert the following VBA code somewhere in the editor (Alt+F11)
execute it line per line (F8)
Sub test()
strCRLF = StrConv(vbCrLf, vbUnicode)
strSpecialchars = StrConv(Cells(1, 1), vbUnicode)
strFilename = "c:\test.txt"
Open strFilename For Output As #1
Print #1, strSpecialchars & strCRLF;
Close #1
End Sub
You will get a textfile which contains the chinese characters from [A1]. This proofs that VBA is able to handle unicode characters if you know the trick with adding StrConv(vbCrLf, vbUnicode)
Now try the same for strFilename = "C:\" & strSpecialchars & ".txt". You will get an error that you can't create a file with this filename. Of course you can't use the same trick adding a new line since its a filename.
How can I create text files with special characters in their filenames using VBA?
Is there a work-around or am I doing something wrong?
Note
I'm using Windows 7 x64. I'm able to create text files with special characters manually
I found a second method using FileSystemObject. But I hope I could avoid setting a reference to the VB script run-time library
Value retrieved from the cell is already in Unicode.
StrConv(vbUnicode) gives you "double unicode" which is broken because it went through a conversion using the current system codepage.
Then the Print command converts it back to "single unicode", again using the current system codepage. Don't do this. You're not saving unicode, you're saving invalid something that may only appear valid on your particular computer under your current settings.
If you want to output Unicode data (that is, avoid the default VB mechanism of auto-converting output text from Unicode to ANSI), you have several options.
The easiest is using FileSystemObject without trying to invent anything about unicode conversions:
With CreateObject("Scripting.FileSystemObject")
With .CreateTextFile("C:\" & Cells(1).Value & ".txt", , True)
.Write Cells(1).Value
.Close
End With
End With
Note the last parameter that controls Unicode.
If you don't want that, you can declare CreateFileW and WriteFile functions:
Private Declare Function CreateFileW Lib "kernel32.dll" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByRef lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function WriteFile Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, ByRef lpNumberOfBytesWritten As Long, ByRef lpOverlapped As Any) As Long
Private Const CREATE_ALWAYS As Long = 2
Private Const GENERIC_WRITE As Long = &H40000000
Dim hFile As Long
hFile = CreateFileW(StrPtr("C:\" & Cells(1).Value & ".txt"), GENERIC_WRITE, 0, ByVal 0&, CREATE_ALWAYS, 0, 0)
Dim val As String
val = Cells(1).Value
WriteFile hFile, &HFEFF, 2, 0, ByVal 0& 'Unicode byte order mark (not required, but to please Notepad)
WriteFile hFile, ByVal StrPtr(val), Len(val) * 2, 0, ByVal 0&
CloseHandle hFile
You are on the right track with the FileSystemObject. As Morbo mentioned you can late bind this so no reference is set. The FSO has a CreateTextFile function which can be set in unicode so the characters will appear as '??????' in VBA but will write correctly to the filename. Note the second parameter of the CreateTextFile function specifies a unicode string for the filename. The following will do the trick for you:
Sub test()
Dim strCRLF As String, strSpecialchars As String, strFilename As String
Dim oFSO As Object, oFile As Object
strCRLF = StrConv(vbCrLf, vbUnicode)
strSpecialchars = StrConv(Cells(1, 1), vbUnicode)
strFilename = "C:\" & Cells(1, 1).Value & ".txt"
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFile = oFSO.CreateTextFile(strFilename, , True)
oFile.Write strSpecialchars & strCRLF
oFile.Close
Set oFile = Nothing
Set oFSO = Nothing
End Sub