I'm trying to make it possible for the data stored in a MSSQL database to be encrypted/decrypted in both Access 2013 as well as ColdFusion. The Access database uses vba to sync data to the SQL database and I've found a few possible solutions for encryption but can't seem to get the results to match the same thing encrypted in ColdFusion.
www.ebcrypt.com appears to be the easiest but when I encrypt with either Blowfish, RIJNDAEL or any of the other methods, the results are not the same as what I encrypt in ColdFusion.
I decided to try to use the native CryptoAPI but the same thing happens when I try to match what vba is doing in ColdFusion I keep getting different results.
I wonder if either the vba or ColdFusion methods I'm using are taking the key I'm passing in and transforming it so it no longer matches. I've tried setting keys manually and even generating it with ColdFusion and then setting it in the vba code to match with no luck.
ColdFusion code trying to use RC4:
<cfset test_key = "ZXNlmehY30y3ophXVJ0EJw==">
<cfset encryptedString = Encrypt("CF String",test_key, "RC4")>
<cfoutput>
Encrypted String: #encryptedString#<br />
Encryption Key: #test_key#
</cfoutput>
VBA Code with the same settings: (clsCryptoFilterBox code is here)
NOTE: It appears that this defaults to RC4, which is why I'm using that in ColdFusion above.
Dim encrypted As clsCryptoFilterBox
Set encrypted = New clsCryptoFilterBox
encrypted.Password = "ZXNlmehY30y3ophXVJ0EJw=="
encrypted.InBuffer = "CF String"
encrypted.Encrypt
MsgBox ("Encrypted: " & encrypted.OutBuffer)
EDIT: Ok, more info. I found that ColdFusion needed the key in base64 even though the variable test_key should have worked but apparently the output of a base64 encoded string is not the same as other text encoded into base64.
EDIT 2: I got it working using the Blowfish algorithm found in the file on this website.
Here is my working CF code:
<cfset test_key = toBase64("1234567812345678")>
<cfset encryptedString = Encrypt("CF String", test_key, "RC4", "HEX")>
<cfoutput>
Encrypted String: #encryptedString#<br />
Encryption Key: #test_key#
</cfoutput>
Which outputs:
Encrypted String: F8B519877DC3B7C997
Encryption Key: MTIzNDU2NzgxMjM0NTY3OA==
I had to modify the code in VBA to pad using PKCS7 but once I did that, I was able to verify that it was working correctly. If anyone is interested I could post my changes to the VBA code where I modified the padding as well as added a check on decryption to verify the data via the padding.
I found a decent Blowfish algorithm packaged in the test app found on this download site that actually works with some modifications.
It was using spaces to pad the input text which is not what ColdFusion was doing, so this was making the encrypted string turn out different. The standard encryption that CF does pads with bytes that are all the same and are set to the number of padding bytes being used.
New EncryptString() function:
Public Function EncryptString(ByVal tString As String, Optional ConvertToHEX As Boolean) As String
Dim ReturnString As String, PartialString As String * 8
Dim tPaddingByte As String
Dim tStrLen As Integer
Dim tBlocks As Integer
Dim tBlockPos As Integer
tStrLen = Len(tString)
'Divide the length of the string by the size of each block and round up
tBlocks = (-Int(-tStrLen / 8))
tBlockPos = 1
Do While tString <> ""
'Check that we are not on the last block
If tBlockPos <> tBlocks Then
'Not on the last block so the string should be over 8 bytes, no need to pad
PartialString = Left$(tString, 8)
Else
'Last block, we need to pad
'Check to see if the last block is 8 bytes so we can create a new block
If Len(tString) = 8 Then
'Block is 8 bytes so add an extra block of padding
tString = tString & String(8, Chr(8))
tPaddingByte = " " 'Not really necessary, just keeps the String() function below happy
Else
'Set the value of the padding byte to the number of padding bytes
tPaddingByte = Chr(8 - Len(tString))
End If
PartialString = Left$(tString & String(8, tPaddingByte), 8)
End If
ReturnString = ReturnString & Encrypt(PartialString)
tString = Mid$(tString, 9)
tBlockPos = tBlockPos + 1
Loop
If ConvertToHEX = True Then
EncryptString = ToHEX(ReturnString)
Else
EncryptString = ReturnString
End If
End Function
Since the padding is not just spaces, it needs to be removed on decryption but there is an easy way to do it that also makes this whole process even better. You read the last byte, and then verify the other padding bytes with it.
Public Function DecryptString(ByVal tString As String, Optional ConvertFromHEX As Boolean) As String
Dim ReturnString As String, PartialString As String * 8
Dim tPos As Integer
Dim tPadCount As Integer
If ConvertFromHEX = True Then
tString = HexToString(tString)
End If
Do While tString <> ""
PartialString = Left$(tString, 8)
ReturnString = ReturnString & Decrypt(PartialString)
tString = Mid$(tString, 9)
Loop
'Check the last byte and verify the padding and then remove it
tPadCount = ToHEX(Right(ReturnString, 1))
If tPadCount < 8 Or tPadCount > 1 Then
'Get all the padding bytes and verify them
Dim tPaddingBytes As String
tPaddingBytes = Right(ReturnString, tPadCount)
Dim i As Integer
For i = 1 To tPadCount
If Not tPadCount = Int(ToHEX(Left(tPaddingBytes, 1))) Then
MsgBox "Error while decrypting: Padding byte incorrect (" & tPadCount & ")"
GoTo Done
End If
Next i
ReturnString = Left(ReturnString, Len(ReturnString) - tPadCount)
Else
MsgBox "Error while decrypting: Last byte incorrect (" & tPadCount & ")"
End If
Done:
DecryptString = ReturnString
End Function
I was able to export the class module and can import it into any other possible projects that may need basic encryption. There is a Rijndael class that appears to be working but in a non-standard way as well that I may get around to fixing later, but for now this is what I was looking for.
Related
Have some code that looks at HTML on the clipboard. It worked on XP but has the following problem on W7. The HTML clipboard header EndHTML value is greater than the string length of the resulting Clipboard.GetText. Both IE11 and Firefox have the same problem. To test browse to: https://stackexchange.com/
edit select all, and copy. Following code snippet to shows the problem. Maybe something to do with encoding?
Dim dto As IDataObject = Clipboard.GetDataObject()
Dim CBdata As String = ""
Dim startHTML As String = ""
Dim endHTML As String = ""
If dto.GetDataPresent(DataFormats.Html) Then
CBdata = Clipboard.GetText(TextDataFormat.Html)
Dim m As Match = Regex.Match(CBdata, "StartHTML:(\d+)")
If m.Success Then startHTML = m.Groups(1).Value
m = Regex.Match(CBdata, "EndHTML:(\d+)")
If m.Success Then endHTML = m.Groups(1).Value
Console.WriteLine("CB data length=" & CBdata.Length.ToString)
Console.WriteLine("EndHtml=" & endHTML)
Console.WriteLine("StartHtml=" & startHTML)
'To get just the html:
'CBdata.Substring(startHTML, endHTML - startHTML)
' but of course throws a subscript exception
End If
The StartHTML and EndHTML counters count bytes, not characters. So if you have a text with one UTF8 character that is encoded in two bytes, you'll get the length of 2 instead of 1.
Now, why it worked in XP and now it doesn't in Win 7. It may be a change in .NET 4.5 that I discovered:
Different behavior of DataObject.GetData(DataFormats.Html) in .NET 4.5
I'm using Exchanged Web Services and would like to retrieve a users "Work Hours". Work hours is a setting on the Calendar and helps with free/busy calculations, but I'd like to get or calculate the actual values.
I have full access to the calendar. If I can use the EWS Managed API that would be my preference. I've searched online, and looked at the GetUserAvailability operation, but I haven't been able to find a method that will give me this data.
If your using Exchange 2010 or later you can get the working hours configuration (documented in http://msdn.microsoft.com/en-us/library/ee202895(v=exchg.80).aspx ) from the IPM.Configuration.WorkHours UserConfiguration FAI object (Folder Associated Items) using the GetUserConfiguration operation in EWS http://msdn.microsoft.com/en-us/library/office/dd899439(v=exchg.150).aspx . eg
UserConfiguration usrConfig = UserConfiguration.Bind(service, "WorkHours", WellKnownFolderName.Calendar, UserConfigurationProperties.All);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new MemoryStream(usrConfig.XmlData));
XmlNodeList nlList = xmlDoc.GetElementsByTagName("WorkHoursVersion1");
Console.WriteLine(nlList.Item(0).InnerXml);
I thought I would update this for VBA, I know it is an old thread but may help people and save them some time. I wrote the following for use in Excel to get to Outlook Calendar settings. I would welcome any feedback and tips on better/neater code writing.
Function GetUserWorkingHours(WHType As String, oCalendarFolder As Object) As String
' Returns user's Calendar Start or End work times
' Uses existing Outlook calendar folder object
' The workinghours data is stored in a hidden Outlook storage binary stream in xml format (no, seriously, it is!)
' ... with a sign on the door saying "beware of the leopard"
'
' Cheshire Catalyst software July 2020
'
Dim olStorage As Object
Dim olPropacc As Object
Dim olBytes() As Byte
Dim a As Variant
Dim xmlString As String ' xml stream text stored here
Dim objDOM As Object ' xml object to parse the xml stream
Dim Result As String ' Holding place for return value
' Loads the hidden Outlook xml store to retrieve WorkingHours
Set olStorage = oCalendarFolder.GetStorage("IPM.Configuration.workhours", 2)
Set olPropacc = olStorage.PropertyAccessor
olBytes = olPropacc.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x7C080102")
' Translate binary stream into text byte by byte (there may be a better way to do this but this way works)
For Each a In olBytes
xmlString = xmlString & Chr(a)
Next a
' Generate the xml object to parse
Set objDOM = CreateObject("Msxml2.DOMDocument.3.0")
' Load the xml stream into the xml parser
objDOM.LoadXML xmlString
' Filter on what we are looking for
Select Case WHType
Case "Start"
Result = objDOM.SelectSingleNode("Root/WorkHoursVersion1/TimeSlot/Start").Text
Case "End"
Result = objDOM.SelectSingleNode("Root/WorkHoursVersion1/TimeSlot/End").Text
Case Else
' Perhaps we should have tested for this before all that messing about with Outlook stores
Result = "Invalid" ' Invalid request
End Select
GetUserWorkingHours = Result
' Tidy up all those objects
Set olStorage = Nothing
Set olPropacc = Nothing
Set objDOM = Nothing
Erase olBytes
End Function
Sub testit()
Dim oOutlook As Object ' Outlook instance
Dim oNS As Object ' Outlook namespace
Dim oCalendar As Object ' Calendar folder of Outlook instance
Set oOutlook = GetObject(, "Outlook.Application")
Set oNS = oOutlook.GetNamespace("MAPI")
Set oCalendar = oNS.GetDefaultFolder(9)
MsgBox ("Start: " & GetUserWorkingHours("Start", oCalendar) & " End: " & GetUserWorkingHours("End", oCalendar))
End Sub
2012/08/31 : Updated my Post
searched all the web for it, found pieces but nothing really helped so i turn to you.
Information about environment:
Programming language is VBA / Access 2003
Data will be read from existing ".ini" File
Data should be inserted into Access Database
Now to my Problem:
I've got a ini file with information inside an ini file. The file looks something like this:
[product_details]
product_description=my product description
product_name=my product
product_price=11.0
product_sku=myproduct2012
these information are saved into "products.ini", when open in notepad or notepad++ it will be displayed correct and can be inserted into my access database and i can display these information in my form
but now someone wants to have something like this:
[product_details]
product_description=мое описание продукта
product_name=мой продукт
product_price=11.0
product_sku=произведение2012
when loading these information via GetINIValue the Value will be saved into Database as unreadable text.
edit: also in Notepad / Notepad++ it is displayed correct, so the cyrillic chars are transferred correct into the ini-file
I really tried many things (using UNICODE Version of GetINIValue, get Code of Char etc., check if Cyrillic text) nothing helped.
What it should do:
I need help to get the Value from this ini entry no matter what language (in this case, English, German, french, Russian are just enough)
Hope someone could help me.
Edit: I've tried Remou's Testing with this Peace of Code open it up by following:
Dim SQL As String
Dim strValue As String
strValue = GetValueOf("product_details","product_description","C:\cyrillic.txt")
SQL = "UPDATE [products] SET [product_description]='" & strValue & "' WHERE [product_id]=23;"
CurrentDb.Execute SQL,dbseechanges
Heres the Code of my Function to read out the Specific Line i need:
Public Function GetValueOf(ByVal Section As String, ByVal Entry As String, ByVal File As String)
Dim fs As New FileSystemObject
Dim ts As TextStream
Dim temp As String
Dim response As String
Dim intresponses As String
Dim SectionFoundBegin As Boolean
Dim SectionFoundEnd As Boolean
Dim DoNext As Boolean
Dim Parse() As String
Dim Finished As Boolean
SectionFoundBegin = False
SectionFoundEnd = False
Set ts = fs.OpenTextFile(File, ForReading, , TristateTrue)
response = ""
intresponses = 1
Finished = False
Do
DoNext = False
temp = ts.ReadLine
If (Not Finished) Then
If (temp = "[" & Section & "]") And Not DoNext Then
SectionFoundBegin = True
DoNext = True
End If
If ((InStr(1, temp, "[") > 0) And (SectionFoundBegin)) And Not DoNext Then
SectionFoundEnd = True
DoNext = True
End If
If (SectionFoundBegin And Not SectionFoundEnd) And Not DoNext Then
If (InStr(1, temp, "=") > 0) Then
Parse = Split(temp, "=")
If (Parse(0) = Entry) Then
While (intresponses <= UBound(Parse))
response = response + Parse(intresponses)
intresponses = intresponses + 1
Wend
DoNext = True
Else
DoNext = True
End If
Else
DoNext = True
End If
End If
End If
Loop Until ts.AtEndOfStream
GetValueOf = response
End Function
What i need:
Something like:
"UPDATE [products] SET [product_description]='мое описание продукта' WHERE [product_id]=23;"
What i get:
"UPDATE [products] SET [product_description]='??? ???????? ????????' WHERE [product_id]=23;"
UPDATE:
Well now i really your help:
I've inserted the following Code:
Public Function GetUnicodeValueOf(ByVal Section As String, ByVal Entry As String, ByVal File As String)
Dim fs As Object
Dim ts As Object
Set fs = CreateObject("Scripting.FileSystemObject")
Dim temp As String
Dim strResponse As String
Dim intResponses As Integer
Dim SectionFoundBegin As Boolean
Dim SectionFoundEnd As Boolean
Dim DoNext As Boolean
Dim Parse() As String
Dim Finished As Boolean
On Error GoTo Error_GetUnicodeValueOf
SectionFoundBegin = False
SectionFoundEnd = False
Set ts = fs.OpenTextFile(File, ForReading, , TristateTrue)
strResponse = ""
intResponses = 1
Finished = False
Do
DoNext = False
temp = ts.ReadLine
If (Not Finished) Then
If (temp = "[" & Section & "]") And Not DoNext Then
SectionFoundBegin = True
DoNext = True
End If
If ((InStr(1, temp, "[") > 0) And (SectionFoundBegin)) And Not DoNext Then
SectionFoundEnd = True
DoNext = True
End If
If (SectionFoundBegin And Not SectionFoundEnd) And Not DoNext Then
If (InStr(1, temp, "=") > 0) Then
Parse = Split(temp, "=")
If (Parse(0) = Entry) Then
While (intResponses <= UBound(Parse))
strResponse = strResponse + Parse(intResponses)
intResponses = intResponses + 1
Finished = True
Wend
DoNext = True
Else
DoNext = True
End If
Else
DoNext = True
End If
End If
End If
Loop Until ts.AtEndOfStream
Exit_GetUnicodeValueOf:
GetUnicodeValueOf = strResponse
Exit Function
Error_GetUnicodeValueOf:
ActionLogging "Fehler beim Parsen der Datei '" & File & "'"
Resume Exit_GetUnicodeValueOf
End Function
by using this file (saved as UTF-8 without BOM) on my Harddisc:
[product_details]
manufacturer_name=
product_id=50
sku=BU-01722
set=4
type=simple
type_id=simple
color=11
ean=
name=Колесникова
description=[LANGTEXT] Колесникова Е.В Я считаю до двадцати [Рабочая тетрадь] 6-7л
short_description=[KURZTEXT] Колесникова Е.В
old_id=
weight=1.0000
news_from_date=
news_to_date=
status=1
url_key=kolesnikova
url_path=kolesnikova.html
visibility=4
gift_message_available=2
required_options=0
has_options=0
image_label=
small_image_label=
thumbnail_label=
created_at=2012-06-25 07:58:29
updated_at=2012-07-27 09:06:24
price=2.0000
special_price=
special_from_date=
special_to_date=
cost=
tax_class_id=2
minimal_price=
enable_googlecheckout=1
meta_title=
meta_keyword=
meta_description=
is_recurring=0
recurring_profile=
custom_design=
custom_design_from=
custom_design_to=
custom_layout_update=
page_layout=
options_container=container2
and i need to have:
[LANGTEXT] Колесникова Е.В Я считаю до двадцати [Рабочая тетрадь] 6-7л
from INI-Key: description
into my access database.
First it works as it should but now when i'm loading a file that is saved with "TriStateTrue"
everything ends up in : ?????????????????????????????????????????????
in one line.
With TriStateMixed, everything is parsed well except of the cyrillic text which comes like
КолеÑникова Е.Ð’ Я Ñчитаю до двадцати [Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ñ‚ÐµÑ‚Ñ€Ð°Ð´ÑŒ] 6-7л
i searched the sourcecode and didn't found the error.
FILE is UTF-8 without BOM (coming from selfwritten Web API for Magento)
Using Access 2003
Need to get Cyrillic Text into my Database where also German / English Texts could be inside the File
Long time ago, i asked this Question and finally got the answer, but because of the lack of time i didn't managed to "Answer myself" here and for other who might have these problems.
First of all, about the Read-Problem:
The Edit from my Question with TryStateTrue was the Right answer, this was the correct line which was needed to load
But now there's the Catch:
The Rules in VBA(6 or lower) are simple:
What will be saved in an String will be stored as ASCII Value. So every Char which is not an ASCII Code will be thrown away and saved as "?"
How did i managed to save those Data?
I Managed to save those Data by using an selfwritten Tool in C# (.NET) which can Handle UTF-8 Strings and can Connect to the Database.
Save Section + Key in List or set as Executable Parameters and where you will "UPDATE" the Value
e.g.:
[product_details]\name;productsTableName;productsNameField;IdentKeyField;IdentKeyValue
open Executable with Arguments or without and load the List
Connect to the desired Access-Database
Read the Section\Key-Value and Send to the Database directly by UPDATE-STATEMENT
e.g:
"UPDATE [productsTableName] SET [productsNameField]='" + ValueFromSectionKey + "' WHERE [IdentKeyField]=IdentKeyValue
Disconnect Database
Close Program
The Result:
a little bit slower at first because writing down what Huge List of Informations
also Writing down everything inside the Database, also with Errors (?????? instead of считаю) secures that if your file is ASCII-"readable" you didn't forget anything
beautiful UTF-8-Encoded and Readable Text inside an Access 2003 Database
The Pros about this Method
outsourced and expendable Tool, when written correctly it can be used for other projects too
understanable Code in Access (you write down informations, and after everything was listed you open up a Program which process these)
very fast when optimized (read the Length and split the list into multiple workers which update the database simultanously)
The Cons about this Method
outsourced
no possibility to save directly into a variable inside VBA(6 or lower)
external tool could be blocked by firewall
before "updating" Database there is unreadable Text inside the Database
more Update-Calls on Database as directly
user-typos inside list or Text containing the delimiter may let the UPDATE statement fail.
Hope i could help.
I have downloaded the Faceboojk profile ID's of all of our Facebook fans to an access databse and have written a function to pull the locale data from the Facebook graph api. Here is an example of the graph api:-
http://graph.facebook.com/665117534
My code loops through each record and pulls teh data from the API like this:-
Function fbl(fb_user_id As String) As String
Dim oXMLHTTP As Object, i As Long, vFF As Long, oResp() As Byte
Dim FB_URL As String
Dim fb_user_data As String
Set oXMLHTTP = CreateObject("MSXML2.XMLHTTP")
FB_URL = "http://graph.facebook.com/" & fb_user_id
oXMLHTTP.Open "GET", FB_URL, False
oXMLHTTP.Send
Do While oXMLHTTP.readyState <> 4
DoEvents
Loop
fb_user_data = oXMLHTTP.responsetext
n0 = InStr(1, fb_user_data, "locale") '
If n0 = 0 Then
locale_code = "PAGE"
Else
n00 = InStr(n0, fb_user_data, "}") '
locale_code = Mid(fb_user_data, n0 + 6, n00 - n0)
locale_code = Replace(locale_code, """", "")
locale_code = Replace(locale_code, ",", "")
locale_code = Replace(locale_code, " ", "")
locale_code = Mid(locale_code, 5, Len(locale_code) - 6)
End If
fbl = locale_code
Set oXMLHTTP = Nothing
End Function
This works fine but testing a 100 records took 35 seconds which suggests that the whole table will take 5.6 hours. Is theire a more efficent, speedier way of doing this?
Thanks
Jonathan
My approach has been to use a multi threaded C# application with System.NET.Sockets and turn off the Nagle Algorithm.
I haven't seen many high performance computing applications written in VBA
Edit
Since you're using the XMLHTTP object you should retag this question to reflect that. You may get a better response from people who know that API.
I am trying to create a button on my access form that allows for the user to view the corresponding page that goes with the data within the form (In this case, a part number is displayed on the form, and I want the button to open the Part Standard file to show the blueprint/diagram of said part)
I have tried using Adobe's page parameters #page=pagenum at the end of my filepath, but doing this doesn't work.
Here is the code I have (Basic, I know) but I'm trying to figure out where to go here. I have simple condensed down my filepath, for obvious reasons - Note: It's not a URL, but a file path if this matters.
Private Sub Command80_Click()
Dim loc As String 'location of file
'loc = Me.FileLoc
loc = "G:\*\FileName.pdf#page=1"
Debug.Print loc
'Debug.Print Me.FileLoc
'Debug.Print Me.FileName
Application.FollowHyperlink loc
End Sub
Is this possible to do this way? I will continue to read other users posts in hopes to find a solution, and I'll note here if I do find one.
Thanks!
Update
I've found a way to do this, just I have 1 small complication now. My database will be accessed by many users, possibly with different versions of Acrobat, or different locations. Here is my working code:
Private Sub Command2_Click()
pat1 = """C:\Program Files\Adobe\Reader 9.0\Reader\AcroRd32.exe"""
pat2 = "/A ""page=20"""
pat3 = """G:\*\FileName.pdf"""
Shell pat1 & " " & pat2 & " " & pat3, vbNormalFocus
End Sub
Now, here is my concern. This code opens AcroRd32.exe from a specific file path, if my users have this stored elsewhere or have a different version, this won't work. Does anyone have a suggestion as how to possibly get around this?
Thanks again! :)
The correct way to do this is probably to look up the location of the acrobat reader executable in the system registry. I find that's generally more trouble than it's worth, especially if I have some control over all of the places my program will be installed (within a single intranet, for example). Usually I end up using this function that I wrote:
'---------------------------------------------------------------------------------------
' Procedure : FirstValidPath
' Author : Mike
' Date : 5/23/2008
' Purpose : Returns the first valid path found in a list of potential paths.
' Usage : Useful for locating files or folders that may be in different locations
' on different users' computers.
' Notes - Directories must be passed with a trailing "\" otherwise the function
' will assume it is looking for a file with no extension.
' - Returns Null if no valid path is found.
' 5/6/11 : Accept Null parameters. If all parameters are Null, Null is returned.
'---------------------------------------------------------------------------------------
'
Function FirstValidPath(ParamArray Paths() As Variant) As Variant
Dim i As Integer
FirstValidPath = Null
If UBound(Paths) - LBound(Paths) >= 0 Then
For i = LBound(Paths) To UBound(Paths)
If Not IsNull(Paths(i)) Then
If Len(Dir(Paths(i))) > 0 Then
FirstValidPath = Paths(i)
Exit For
End If
End If
Next i
End If
End Function
The function takes a parameter array so you can pass it as many or as few paths as necessary:
PathToUse = FirstValidPath("C:\Program Files\Adobe\Reader 9.0\Reader\AcroRd32.exe", _
"C:\Program Files\Acrobat\Reader.exe", _
"C:\Program Files (x86)\Acrobat\Reader.exe", _
"C:\Program Files\Acrobat\12\Reader.exe")
pat1 = """" & PathToUse & """"
Registry keys are the better way to go, unlike file locations they have consistency between systems.
Below are three functions, two in support of one, and a macro which tests the functions.
GetARE() (Get Adobe Reader Executable) returns the proper path based on a version search in a pre-defined location passed as the argument. This removes the hassle of typing out many different key locations for each version and provides some amount of coverage should future versions be released and installed on a user's system.
I have installed previous versions of Reader to test whether or not the there is consistency in the InstallPath key location, up until quite outdated versions, there is. In fact, mwolfe02 and I both have our keys in the same location, though I am using version 11 and he, at the time of writing, was using 10. I was only able to test this on a x64 system, but you can easily modify the code below to search for both x64 and x86 keys. I expect a large corporation like Adobe to stick to their conventions, so this will likely work for quite some time without much modification even as new versions of Reader are released.
I wrote this quickly, expect inefficiency and inconsistency in naming conventions.
Truly the best approach to ensure the path is almost-always returned would be to simply run a registry search through VBA in a loop for version numbers using "*/Acrobat Reader/XX.YY/InstallPath/" and then including the executable based on a check for the appropriate candidate in the appropriate directory; however, this isn't exactly a very cost-effective solution. My tests have shown that there is quite a bit of consistency between versions as to where the Install Path can be found, and as to what the executable name may be, so I opted for something more efficient if less lasting.
RegKeyRead() and RegKeyExists() were taken from:
http://vba-corner.livejournal.com/3054.html
I have not modified their code. Take into consideration saying thanks to the author of that post, the code is not complex by any means but it did save me the hassle of writing it myself.
Function RegKeyRead(i_RegKey As String) As String
Dim myWS As Object
On Error Resume Next
'access Windows scripting
Set myWS = CreateObject("WScript.Shell")
'read key from registry
RegKeyRead = myWS.RegRead(i_RegKey)
End Function
Function RegKeyExists(i_RegKey As String) As Boolean
Dim myWS As Object
On Error GoTo ErrorHandler
'access Windows scripting
Set myWS = CreateObject("WScript.Shell")
'try to read the registry key
myWS.RegRead i_RegKey
'key was found
RegKeyExists = True
Exit Function
ErrorHandler:
'key was not found
RegKeyExists = False
End Function
Function GetARE(i_RegKey As String) As String
Dim InPath As String
Dim InKey As String
Dim Ind As Integer
Dim PriVer As String
Dim SubVer As String
Dim Exists As Boolean
Exists = False
PriVer = 1
SubVer = 0
For Ind = 1 To 1000
If SubVer > 9 Then
PriVer = PriVer + 1
SubVer = 0
End If
Exists = RegKeyExists(i_RegKey + "\" + PriVer + "." + SubVer + "\InstallPath\")
SubVer = SubVer + 1
If Exists = True Then
SubVer = SubVer - 1
InKey = i_RegKey + "\" + PriVer + "." + SubVer + "\InstallPath\"
InPath = RegKeyRead(InKey)
GetARE = InPath + "\AcroRd32.exe"
Exit For
End If
Next
End Function
Sub test()
Dim rando As String
rando = GetARIP("HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Acrobat Reader")
MsgBox (rando)
End Sub
I remember that Acrobat reader used to include some ActiveX PDF reader object available for further use with Microsoft Office. Other companies have developed similar products, some of them (in their basic form) even available for free.
That could be a solution, couldn't it? You'd have then to check that your activeX PDF reader supports direct page access in its methods, and distribute it with your apps, or have it installed on your user's computers. It will avoid you all the overhead related to acrobat readers versions follow-up, specially when newer versions will be available on the market and you'll have to update your client interface.
Just to add to mwolfe02's answer, here is a function that tries to retrieve the executable for the file type given (it also uses the registry commands Levy referenced) :
Function GetShellFileCommand(FileType As String, Optional Command As String)
Const KEY_ROOT As String = "HKEY_CLASSES_ROOT\"
Dim sKey As String, sProgramClass As String
' All File Extensions should start with a "."
If Left(FileType, 1) <> "." Then FileType = "." & FileType
' Check if the File Extension Key exists and Read the default string value
sKey = KEY_ROOT & FileType & "\"
If RegKeyExists(sKey) Then
sProgramClass = RegKeyRead(sKey)
sKey = KEY_ROOT & sProgramClass & "\shell\"
If RegKeyExists(sKey) Then
' If no command was passed, check the "shell" default string value, for a default command
If Command = vbNullString Then Command = RegKeyRead(sKey)
' If no Default command was found, default to "Open"
If Command = vbNullString Then Command = "Open"
' Check for the command
If RegKeyExists(sKey & Command & "\command\") Then GetShellFileCommand = RegKeyRead(sKey & Command & "\command\")
End If
End If
End Function
so,
Debug.Print GetShellFileEx("PDF")
outputs:
"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe" "%1"
and you just have to replace the "%1" with the file you want to open and add any parameters you need.
Here is code the probably you can use..
Private Sub CommandButton3_Click()
Dim strFile As String
R = 0
If TextBox7 = "CL" Then
R = 2
' Path and filename of PDF file
strFile = "E:\Users\Test\Cupertino Current system.pdf"
ActiveWorkbook.FollowHyperlink strFile
End If
if R = 0 Then
MsgBox "Wrong Code"
ComboBox1 = ""
TextBox1 = Empty
'ComboBox1.SetFocus
End If
End Sub
Just need to the right path.. Hope this can help you