Rectangle 27 486

VBA

Excel 2007 Excel 2010 Excel 2013 - 32 bit version. Excel 2016 - 32 bit version.

  • The VBE will call a system function to create the password dialog box.
  • if this value is 1, Excel will "think" that the password is right, hence the locked VBA project will be opened.
  • If user enters the right password and click OK, this function returns 1. If user enters the wrong password or click Cancel, this function returns 0.
  • After the dialog box is closed, the VBE checks the returned value of the system function
  • if this value is 1, the VBE will "think" that the password is right, hence the locked VBA project will be opened.
  • The code below swaps the memory of the original function used to display the password dialog with a user defined function that will always return 1 when being called.
  • Open the file(s) that contain your locked VBA Projects
  • Create a new xlsm file and store this code in Module1 code credited to Siwtom (nick name), a Vietnamese developer Option Explicit Private Const PAGE_EXECUTE_READWRITE = &H40 Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Long, Source As Long, ByVal Length As Long) Private Declare Function VirtualProtect Lib "kernel32" (lpAddress As Long, _ ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, _ ByVal lpProcName As String) As Long Private Declare Function DialogBoxParam Lib "user32" Alias "DialogBoxParamA" (ByVal hInstance As Long, _ ByVal pTemplateName As Long, ByVal hWndParent As Long, _ ByVal lpDialogFunc As Long, ByVal dwInitParam As Long) As Integer Dim HookBytes(0 To 5) As Byte Dim OriginBytes(0 To 5) As Byte Dim pFunc As Long Dim Flag As Boolean Private Function GetPtr(ByVal Value As Long) As Long GetPtr = Value End Function Public Sub RecoverBytes() If Flag Then MoveMemory ByVal pFunc, ByVal VarPtr(OriginBytes(0)), 6 End Sub Public Function Hook() As Boolean Dim TmpBytes(0 To 5) As Byte Dim p As Long Dim OriginProtect As Long Hook = False pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "DialogBoxParamA") If VirtualProtect(ByVal pFunc, 6, PAGE_EXECUTE_READWRITE, OriginProtect) <> 0 Then MoveMemory ByVal VarPtr(TmpBytes(0)), ByVal pFunc, 6 If TmpBytes(0) <> &H68 Then MoveMemory ByVal VarPtr(OriginBytes(0)), ByVal pFunc, 6 p = GetPtr(AddressOf MyDialogBoxParam) HookBytes(0) = &H68 MoveMemory ByVal VarPtr(HookBytes(1)), ByVal VarPtr(p), 4 HookBytes(5) = &HC3 MoveMemory ByVal pFunc, ByVal VarPtr(HookBytes(0)), 6 Flag = True Hook = True End If End If End Function Private Function MyDialogBoxParam(ByVal hInstance As Long, _ ByVal pTemplateName As Long, ByVal hWndParent As Long, _ ByVal lpDialogFunc As Long, ByVal dwInitParam As Long) As Integer If pTemplateName = 4070 Then MyDialogBoxParam = 1 Else RecoverBytes MyDialogBoxParam = DialogBoxParam(hInstance, pTemplateName, _ hWndParent, lpDialogFunc, dwInitParam) Hook End If End Function

Tested and works on .xlsm file from Excel 2013.

This needs to be upvoted a lot.

  • Paste this code in Module2 and run it Sub unprotected() If Hook Then MsgBox "VBA Project is unprotected!", vbInformation, "*****" End If End Sub

Brilliant answer, the standout solution here

Some explanation would be nice of how this is working.

Now the only question left (after seeing this impressive method works perfectly), is how the hell can I make my VBA project protected stronger to prevent others from using this hack on it :)

This code works perfectly in unlocking the VBA code although each time I have used this it prevents me from re-protecting the project with a different password, has anyone else had this problem?

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 473

VBA

Excel 2007 Excel 2010 Excel 2013 - 32 bit version. Excel 2016 - 32 bit version.

  • The VBE will call a system function to create the password dialog box.
  • If user enters the right password and click OK, this function returns 1. If user enters the wrong password or click Cancel, this function returns 0.
  • After the dialog box is closed, the VBE checks the returned value of the system function
  • if this value is 1, the VBE will "think" that the password is right, hence the locked VBA project will be opened.
  • The code below swaps the memory of the original function used to display the password dialog with a user defined function that will always return 1 when being called.
  • Open the file(s) that contain your locked VBA Projects
  • Create a new xlsm file and store this code in Module1 code credited to Siwtom (nick name), a Vietnamese developer Option Explicit Private Const PAGE_EXECUTE_READWRITE = &H40 Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Long, Source As Long, ByVal Length As Long) Private Declare Function VirtualProtect Lib "kernel32" (lpAddress As Long, _ ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, _ ByVal lpProcName As String) As Long Private Declare Function DialogBoxParam Lib "user32" Alias "DialogBoxParamA" (ByVal hInstance As Long, _ ByVal pTemplateName As Long, ByVal hWndParent As Long, _ ByVal lpDialogFunc As Long, ByVal dwInitParam As Long) As Integer Dim HookBytes(0 To 5) As Byte Dim OriginBytes(0 To 5) As Byte Dim pFunc As Long Dim Flag As Boolean Private Function GetPtr(ByVal Value As Long) As Long GetPtr = Value End Function Public Sub RecoverBytes() If Flag Then MoveMemory ByVal pFunc, ByVal VarPtr(OriginBytes(0)), 6 End Sub Public Function Hook() As Boolean Dim TmpBytes(0 To 5) As Byte Dim p As Long Dim OriginProtect As Long Hook = False pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "DialogBoxParamA") If VirtualProtect(ByVal pFunc, 6, PAGE_EXECUTE_READWRITE, OriginProtect) <> 0 Then MoveMemory ByVal VarPtr(TmpBytes(0)), ByVal pFunc, 6 If TmpBytes(0) <> &H68 Then MoveMemory ByVal VarPtr(OriginBytes(0)), ByVal pFunc, 6 p = GetPtr(AddressOf MyDialogBoxParam) HookBytes(0) = &H68 MoveMemory ByVal VarPtr(HookBytes(1)), ByVal VarPtr(p), 4 HookBytes(5) = &HC3 MoveMemory ByVal pFunc, ByVal VarPtr(HookBytes(0)), 6 Flag = True Hook = True End If End If End Function Private Function MyDialogBoxParam(ByVal hInstance As Long, _ ByVal pTemplateName As Long, ByVal hWndParent As Long, _ ByVal lpDialogFunc As Long, ByVal dwInitParam As Long) As Integer If pTemplateName = 4070 Then MyDialogBoxParam = 1 Else RecoverBytes MyDialogBoxParam = DialogBoxParam(hInstance, pTemplateName, _ hWndParent, lpDialogFunc, dwInitParam) Hook End If End Function
  • Paste this code in Module2 and run it Sub unprotected() If Hook Then MsgBox "VBA Project is unprotected!", vbInformation, "*****" End If End Sub

Tested and works on .xlsm file from Excel 2013.

Works perfect for me on excel 2010 as well...

Some explanation would be nice of how this is working.

This code works perfectly in unlocking the VBA code although each time I have used this it prevents me from re-protecting the project with a different password, has anyone else had this problem?

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 360

Dim
Dim rng as Range

Set the variable to the required range. There are many ways to refer to a single-cell range

Set rng = Range("A1")
Set rng = Cells(1,1)
Set rng = [A1]
Set rng = Range("NamedRange")

or a multi-cell range

Set rng = Range("A1:B10")
Set rng = Range(Cells(1,1), Cells(2,10))
Set rng = [A1:B10]
Set rng = Range("AnotherNamedRange")

All the above examples refer to cells on the active sheet. Unless you specifically want to work only with the active sheet, it is better to Dim a Worksheet variable too

Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Set rng = ws.Cells(1,1)
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")

Pass ranges to your Sub's and Function's as Range variables

Sub ClearRange(r as Range)
    r.ClearContents
    '....
End Sub

Sub MyMacro()
    Dim rng as Range
    Set rng = [A1:B10]
    ClearRange rng
End Sub

You should also apply Methods (such as Find and Copy) to variables

Dim rng1 As Range
Dim rng2 As Range
Set rng1 = [A1:A10]
Set rng2 = [B1:B10]
rng1.Copy rng2

If you are looping over a range of cells it is often better (faster) to copy the range values to a variant array first and loop over that

Dim dat As Variant
Dim rng As Range
Dim i As Long

Set rng = [A1:A10000]
dat = rng.Value  ' dat is now array (1 to 10000, 1 to 1)
for i = LBound(dat, 1) to UBound(dat, 1)
    dat(i,1) = dat(i,1) * 10 'or whatever operation you need to perform
next
rng.Value = dat ' put new values back on sheet

@qbik not advocating using a Sub to just clear a range (as the ... indicates), rather it's a demo of passing a Range as a parameter to a Sub.

adding to this brilliant answer that in order to work wit a range you don't need to know its actual size as long as you know the top left ... e.g. rng1(12, 12) will work even though rng1 was set to [A1:A10] only.

@chrisneilsen Chris, I believe you can also use worksheet prefix before shorthand cell reference notation to save you from typing Range like this: ActiveSheet.[a1:a4] or ws.[b6].

@mertinc yes you tagged correctly but if you have a question you should ask a question, not comment on an old thread

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 350

Dim
Dim rng as Range

Set the variable to the required range. There are many ways to refer to a single-cell range

Set rng = Range("A1")
Set rng = Cells(1,1)
Set rng = [A1]
Set rng = Range("NamedRange")

or a multi-cell range

Set rng = Range("A1:B10")
Set rng = Range(Cells(1,1), Cells(2,10))
Set rng = [A1:B10]
Set rng = Range("AnotherNamedRange")

All the above examples refer to cells on the active sheet. Unless you specifically want to work only with the active sheet, it is better to Dim a Worksheet variable too

Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Set rng = ws.Cells(1,1)
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")

Pass ranges to your Sub's and Function's as Range variables

Sub ClearRange(r as Range)
    r.ClearContents
    '....
End Sub

Sub MyMacro()
    Dim rng as Range
    Set rng = [A1:B10]
    ClearRange rng
End Sub

You should also apply Methods (such as Find and Copy) to variables

Dim rng1 As Range
Dim rng2 As Range
Set rng1 = [A1:A10]
Set rng2 = [B1:B10]
rng1.Copy rng2

If you are looping over a range of cells it is often better (faster) to copy the range values to a variant array first and loop over that

Dim dat As Variant
Dim rng As Range
Dim i As Long

Set rng = [A1:A10000]
dat = rng.Value  ' dat is now array (1 to 10000, 1 to 1)
for i = LBound(dat, 1) to UBound(dat, 1)
    dat(i,1) = dat(i,1) * 10 'or whatever operation you need to perform
next
rng.Value = dat ' put new values back on sheet

@qbik not advocating using a Sub to just clear a range (as the ... indicates), rather it's a demo of passing a Range as a parameter to a Sub.

adding to this brilliant answer that in order to work wit a range you don't need to know its actual size as long as you know the top left ... e.g. rng1(12, 12) will work even though rng1 was set to [A1:A10] only.

@chrisneilsen Chris, I believe you can also use worksheet prefix before shorthand cell reference notation to save you from typing Range like this: ActiveSheet.[a1:a4] or ws.[b6].

@mertinc yes you tagged correctly but if you have a question you should ask a question, not comment on an old thread

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 150

.Select
.Activate
Selection
Activecell
Activesheet
Activeworkbook
  • It is usually the main cause of runtime errors.

1) Directly work with the relevant objects

Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"

This code can also be written as

With Sheets("Sheet1").Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With

2) If required declare your variables. The same code above can be written as

Dim ws as worksheet

Set ws = Sheets("Sheet1")

With ws.Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With

That's a good answer, but what I am missing on this topic is when we actually need Activate. Everyone says it is bad, but no one explains any cases where it makes sense to use it. For example I was working with 2 workbooks and could not start a macro on one of the workbooks without activating it first. Could you elaborate a bit maybe? Also if for example I do not activate sheets when copying a range from one sheet to another, when I execute the program, it seems to activate the respective sheets anyways, implicitly.

I find that you may sometimes need to activate a sheet first if you need to paste or filter data on it. I would say its best to avoid activating as much as possible but there are instances where you need to do it. So keep activating and selecting to an absolute minimum as per the answer above.

i think the point is not to completely avoid using them, but just as much as possible. if you want to save a workbook, so that when someone opens it a certain cell in a certain sheet is selected, then you have to select that sheet and cell. copy/paste is a bad example, at least in the case of values, it can be done faster by a code such as Sheets(2).[C10:D12].Value = Sheets(1).[A1:B3].Value

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 149

.Select
.Activate
Selection
Activecell
Activesheet
Activeworkbook
  • It is usually the main cause of runtime errors.

1) Directly work with the relevant objects

Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"

This code can also be written as

With Sheets("Sheet1").Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With

2) If required declare your variables. The same code above can be written as

Dim ws as worksheet

Set ws = Sheets("Sheet1")

With ws.Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With

That's a good answer, but what I am missing on this topic is when we actually need Activate. Everyone says it is bad, but no one explains any cases where it makes sense to use it. For example I was working with 2 workbooks and could not start a macro on one of the workbooks without activating it first. Could you elaborate a bit maybe? Also if for example I do not activate sheets when copying a range from one sheet to another, when I execute the program, it seems to activate the respective sheets anyways, implicitly.

I find that you may sometimes need to activate a sheet first if you need to paste or filter data on it. I would say its best to avoid activating as much as possible but there are instances where you need to do it. So keep activating and selecting to an absolute minimum as per the answer above.

i think the point is not to completely avoid using them, but just as much as possible. if you want to save a workbook, so that when someone opens it a certain cell in a certain sheet is selected, then you have to select that sheet and cell. copy/paste is a bad example, at least in the case of values, it can be done faster by a code such as Sheets(2).[C10:D12].Value = Sheets(1).[A1:B3].Value

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 193

Yes there is, as long as you are using a .xls format spreadsheet (the default for Excel up to 2003). For Excel 2007 onwards, the default is .xlsx, which is a fairly secure format, and this method will not work.

As Treb says, it's a simple comparison. One method is to simply swap out the password entry in the file using a hex editor (see Hex editors for Windows). Step by step example:

  • Create a new simple excel file.
  • In the VBA part, set a simple password (say - 1234).

Then check the file size - see Stewbob's gotcha

  • Open the file you just created with a hex editor.

Copy the lines starting with the following keys:

CMG=....
DPB=...
GC=...

FIRST BACKUP the excel file you don't know the VBA password for, then open it with your hex editor, and paste the above copied lines from the dummy file.

  • Save the excel file and exit.
  • Now, open the excel file you need to see the VBA code in. The password for the VBA code will simply be 1234 (as in the example I'm showing here).

If you need to work with Excel 2007 or 2010, there are some other answers below which might help, particularly these: 1, 2, 3.

What if there are no lines that start with CMG=...?

In the blank excel file, or the locked one? Check the file size of the blank file. If its the locked file, make sure your backup is safe, then try changing just the other two lines. You sure it's encrypted file?

In the blank file. The other two lines do not appear either. Does this also work in Excel 2007? I used HEdit.

Excel 2007 password protection (and file format) is radically different than Excel 2003. I included some specifics about it in my answer below. In my opinion, the password protected option on an Excel 2007 file is the first time in Microsoft Office history that they have produced a reasonably secure file.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 193

Yes there is, as long as you are using a .xls format spreadsheet (the default for Excel up to 2003). For Excel 2007 onwards, the default is .xlsx, which is a fairly secure format, and this method will not work.

As Treb says, it's a simple comparison. One method is to simply swap out the password entry in the file using a hex editor (see Hex editors for Windows). Step by step example:

  • Create a new simple excel file.
  • In the VBA part, set a simple password (say - 1234).

Then check the file size - see Stewbob's gotcha

  • Open the file you just created with a hex editor.

Copy the lines starting with the following keys:

CMG=....
DPB=...
GC=...

FIRST BACKUP the excel file you don't know the VBA password for, then open it with your hex editor, and paste the above copied lines from the dummy file.

  • Save the excel file and exit.
  • Now, open the excel file you need to see the VBA code in. The password for the VBA code will simply be 1234 (as in the example I'm showing here).

If you need to work with Excel 2007 or 2010, there are some other answers below which might help, particularly these: 1, 2, 3.

What if there are no lines that start with CMG=...?

In the blank excel file, or the locked one? Check the file size of the blank file. If its the locked file, make sure your backup is safe, then try changing just the other two lines. You sure it's encrypted file?

In the blank file. The other two lines do not appear either. Does this also work in Excel 2007? I used HEdit.

Excel 2007 password protection (and file format) is radically different than Excel 2003. I included some specifics about it in my answer below. In my opinion, the password protected option on an Excel 2007 file is the first time in Microsoft Office history that they have produced a reasonably secure file.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 48

Note this was done on Excel for Mac 2011 but should be same for Windows

Sub numberformats()
  Dim rng As Range
  Set rng = Range("A24:A35")
  For Each c In rng
    Debug.Print c.NumberFormat
  Next c
End Sub
General     General
Number      0
Currency    $#,##0.00;[Red]$#,##0.00
Accounting  _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)
Date        m/d/yy
Time        [$-F400]h:mm:ss am/pm
Percentage  0.00%
Fraction    # ?/?
Scientific  0.00E+00
Text        @
Special     ;;
Custom      #,##0_);[Red](#,##0)

(I just picked a random entry for custom)

The "s of Accounting need to be escaped, "" to prevent bugs in vba that uses that code.

What are .NumberFormat Options In Excel VBA? - Stack Overflow

excel vba excel-vba excel-2010
Rectangle 27 145

There is another (somewhat easier) solution, without the size problems. I used this approach today (on a 2003 XLS file, using Excel 2007) and was successful.

  • Open the file in a HEX editor and locate the DPB=... part
  • Using a HEX editor, locate the DPB=... part
DPB=...
  • Open the VBA editor (ALT+F11)
DPx=...
  • Open the xls file in Excel
  • Open the VBA editor (ALT + F11)
  • the magic: Excel discovers an invalid key (DPx) and asks whether you want to continue loading the project (basically ignoring the protection)
  • Close and reopen the document and work your VBA magic!

*NOTE: Be sure that you have changed the password to a new value, otherwise the next time you open the spreadsheet Excel will report errors (Unexpected Error), then when you access the list of VBA modules you will now see the names of the source modules but receive another error when trying to open forms/code/etc. To remedy this, go back to the VBA Project Properties and set the password to a new value. Save and re-open the Excel document and you should be good to go!

This method worked great on an Excel 2000 .xls file.

Unfortunately, this didn't work for me with Excel for Mac 2011 v14.2.5. I got the option to repair the file, not to reset the password, and the effect was losing all the VBA scripts.

Perfect solution - I did this with a 2003 file using HxD Hex Editor

I just tried it (.xls, Excel 2007) and it did not work. Result is: The Modules are visible, code does indeed seem to work, but when opening a module, it says unexpected error (40230).

Same error here (Excel 2010) - but then I realised I'd skipped the 'set a new password and save/reopen' (steps 7-9) from Pieter.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 144

There is another (somewhat easier) solution, without the size problems. I used this approach today (on a 2003 XLS file, using Excel 2007) and was successful.

  • Open the file in a HEX editor and locate the DPB=... part
DPB=...
DPx=...
  • Open the xls file in Excel
  • Open the VBA editor (ALT + F11)
  • the magic: Excel discovers an invalid key (DPx) and asks whether you want to continue loading the project (basically ignoring the protection)
  • Close and reopen the document and work your VBA magic!

*NOTE: Be sure that you have changed the password to a new value, otherwise the next time you open the spreadsheet Excel will report errors (Unexpected Error), then when you access the list of VBA modules you will now see the names of the source modules but receive another error when trying to open forms/code/etc. To remedy this, go back to the VBA Project Properties and set the password to a new value. Save and re-open the Excel document and you should be good to go!

This method worked great on an Excel 2000 .xls file.

Unfortunately, this didn't work for me with Excel for Mac 2011 v14.2.5. I got the option to repair the file, not to reset the password, and the effect was losing all the VBA scripts.

Perfect solution - I did this with a 2003 file using HxD Hex Editor

I just tried it (.xls, Excel 2007) and it did not work. Result is: The Modules are visible, code does indeed seem to work, but when opening a module, it says unexpected error (40230).

Same error here (Excel 2010) - but then I realised I'd skipped the 'set a new password and save/reopen' (steps 7-9) from Pieter.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 52

Here's a module for calculating SHA1 hashes that is usable for Excel formulas eg. '=SHA1HASH("test")'. To use it, make a new module called 'module_sha1' and copy and paste it all in. This is based on some VBA code from http://vb.wikia.com/wiki/SHA-1.bas, with changes to support passing it a string, and executable from formulas in Excel cells.

' Based on: http://vb.wikia.com/wiki/SHA-1.bas
Option Explicit

Private Type FourBytes
    A As Byte
    B As Byte
    C As Byte
    D As Byte
End Type
Private Type OneLong
    L As Long
End Type

Function HexDefaultSHA1(Message() As Byte) As String
 Dim H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long
 DefaultSHA1 Message, H1, H2, H3, H4, H5
 HexDefaultSHA1 = DecToHex5(H1, H2, H3, H4, H5)
End Function

Function HexSHA1(Message() As Byte, ByVal Key1 As Long, ByVal Key2 As Long, ByVal Key3 As Long, ByVal Key4 As Long) As String
 Dim H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long
 xSHA1 Message, Key1, Key2, Key3, Key4, H1, H2, H3, H4, H5
 HexSHA1 = DecToHex5(H1, H2, H3, H4, H5)
End Function

Sub DefaultSHA1(Message() As Byte, H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long)
 xSHA1 Message, &H5A827999, &H6ED9EBA1, &H8F1BBCDC, &HCA62C1D6, H1, H2, H3, H4, H5
End Sub

Sub xSHA1(Message() As Byte, ByVal Key1 As Long, ByVal Key2 As Long, ByVal Key3 As Long, ByVal Key4 As Long, H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long)
 'CA62C1D68F1BBCDC6ED9EBA15A827999 + "abc" = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D"
 '"abc" = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D"

 Dim U As Long, P As Long
 Dim FB As FourBytes, OL As OneLong
 Dim i As Integer
 Dim W(80) As Long
 Dim A As Long, B As Long, C As Long, D As Long, E As Long
 Dim T As Long

 H1 = &H67452301: H2 = &HEFCDAB89: H3 = &H98BADCFE: H4 = &H10325476: H5 = &HC3D2E1F0

 U = UBound(Message) + 1: OL.L = U32ShiftLeft3(U): A = U \ &H20000000: LSet FB = OL 'U32ShiftRight29(U)

 ReDim Preserve Message(0 To (U + 8 And -64) + 63)
 Message(U) = 128

 U = UBound(Message)
 Message(U - 4) = A
 Message(U - 3) = FB.D
 Message(U - 2) = FB.C
 Message(U - 1) = FB.B
 Message(U) = FB.A

 While P < U
     For i = 0 To 15
         FB.D = Message(P)
         FB.C = Message(P + 1)
         FB.B = Message(P + 2)
         FB.A = Message(P + 3)
         LSet OL = FB
         W(i) = OL.L
         P = P + 4
     Next i

     For i = 16 To 79
         W(i) = U32RotateLeft1(W(i - 3) Xor W(i - 8) Xor W(i - 14) Xor W(i - 16))
     Next i

     A = H1: B = H2: C = H3: D = H4: E = H5

     For i = 0 To 19
         T = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(A), E), W(i)), Key1), ((B And C) Or ((Not B) And D)))
         E = D: D = C: C = U32RotateLeft30(B): B = A: A = T
     Next i
     For i = 20 To 39
         T = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(A), E), W(i)), Key2), (B Xor C Xor D))
         E = D: D = C: C = U32RotateLeft30(B): B = A: A = T
     Next i
     For i = 40 To 59
         T = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(A), E), W(i)), Key3), ((B And C) Or (B And D) Or (C And D)))
         E = D: D = C: C = U32RotateLeft30(B): B = A: A = T
     Next i
     For i = 60 To 79
         T = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(A), E), W(i)), Key4), (B Xor C Xor D))
         E = D: D = C: C = U32RotateLeft30(B): B = A: A = T
     Next i

     H1 = U32Add(H1, A): H2 = U32Add(H2, B): H3 = U32Add(H3, C): H4 = U32Add(H4, D): H5 = U32Add(H5, E)
 Wend
End Sub

Function U32Add(ByVal A As Long, ByVal B As Long) As Long
 If (A Xor B) < 0 Then
     U32Add = A + B
 Else
     U32Add = (A Xor &H80000000) + B Xor &H80000000
 End If
End Function

Function U32ShiftLeft3(ByVal A As Long) As Long
 U32ShiftLeft3 = (A And &HFFFFFFF) * 8
 If A And &H10000000 Then U32ShiftLeft3 = U32ShiftLeft3 Or &H80000000
End Function

Function U32ShiftRight29(ByVal A As Long) As Long
 U32ShiftRight29 = (A And &HE0000000) \ &H20000000 And 7
End Function

Function U32RotateLeft1(ByVal A As Long) As Long
 U32RotateLeft1 = (A And &H3FFFFFFF) * 2
 If A And &H40000000 Then U32RotateLeft1 = U32RotateLeft1 Or &H80000000
 If A And &H80000000 Then U32RotateLeft1 = U32RotateLeft1 Or 1
End Function
Function U32RotateLeft5(ByVal A As Long) As Long
 U32RotateLeft5 = (A And &H3FFFFFF) * 32 Or (A And &HF8000000) \ &H8000000 And 31
 If A And &H4000000 Then U32RotateLeft5 = U32RotateLeft5 Or &H80000000
End Function
Function U32RotateLeft30(ByVal A As Long) As Long
 U32RotateLeft30 = (A And 1) * &H40000000 Or (A And &HFFFC) \ 4 And &H3FFFFFFF
 If A And 2 Then U32RotateLeft30 = U32RotateLeft30 Or &H80000000
End Function

Function DecToHex5(ByVal H1 As Long, ByVal H2 As Long, ByVal H3 As Long, ByVal H4 As Long, ByVal H5 As Long) As String
 Dim H As String, L As Long
 DecToHex5 = "00000000 00000000 00000000 00000000 00000000"
 H = Hex(H1): L = Len(H): Mid(DecToHex5, 9 - L, L) = H
 H = Hex(H2): L = Len(H): Mid(DecToHex5, 18 - L, L) = H
 H = Hex(H3): L = Len(H): Mid(DecToHex5, 27 - L, L) = H
 H = Hex(H4): L = Len(H): Mid(DecToHex5, 36 - L, L) = H
 H = Hex(H5): L = Len(H): Mid(DecToHex5, 45 - L, L) = H
End Function

' Convert the string into bytes so we can use the above functions
' From Chris Hulbert: http://splinter.com.au/blog

Public Function SHA1HASH(str)
  Dim i As Integer
  Dim arr() As Byte
  ReDim arr(0 To Len(str) - 1) As Byte
  For i = 0 To Len(str) - 1
   arr(i) = Asc(Mid(str, i + 1, 1))
  Next i
  SHA1HASH = Replace(LCase(HexDefaultSHA1(arr)), " ", "")
End Function

As per a user down below, I've copied this into a Access Module and can then call SHA1HASH("TEST") and get a unreadable string back. a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 is an example from a 4 character word.

Works perfectly! I'm on Excel 2013, but I assume it should work on pretty much any version.

Brilliant! Works perfectly and as expected!

This should be the answer! Brilliant!

I'm having a problem with this code. It does exactly what it should when called (great!). However, it also seems to get called randomly when debugging without a whisper of "SHA1HASH(...)". Any ideas why?

Password hash function for Excel VBA - Stack Overflow

excel excel-vba hash sha1
Rectangle 27 93

I've built upon c Thanh Nguyn's fantastic answer to allow this method to work with 64-bit versions of Excel. I'm running Excel 2010 64-Bit on 64-Bit Windows 7.

  • Open the file(s) that contain your locked VBA Projects.
  • Create a new xlsm file and store this code in Module1 Option Explicit Private Const PAGE_EXECUTE_READWRITE = &H40 Private Declare PtrSafe Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As LongPtr, Source As LongPtr, ByVal Length As LongPtr) Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (lpAddress As LongPtr, _ ByVal dwSize As LongPtr, ByVal flNewProtect As LongPtr, lpflOldProtect As LongPtr) As LongPtr Private Declare PtrSafe Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As LongPtr Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, _ ByVal lpProcName As String) As LongPtr Private Declare PtrSafe Function DialogBoxParam Lib "user32" Alias "DialogBoxParamA" (ByVal hInstance As LongPtr, _ ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, _ ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer Dim HookBytes(0 To 5) As Byte Dim OriginBytes(0 To 5) As Byte Dim pFunc As LongPtr Dim Flag As Boolean Private Function GetPtr(ByVal Value As LongPtr) As LongPtr GetPtr = Value End Function Public Sub RecoverBytes() If Flag Then MoveMemory ByVal pFunc, ByVal VarPtr(OriginBytes(0)), 6 End Sub Public Function Hook() As Boolean Dim TmpBytes(0 To 5) As Byte Dim p As LongPtr Dim OriginProtect As LongPtr Hook = False pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "DialogBoxParamA") If VirtualProtect(ByVal pFunc, 6, PAGE_EXECUTE_READWRITE, OriginProtect) <> 0 Then MoveMemory ByVal VarPtr(TmpBytes(0)), ByVal pFunc, 6 If TmpBytes(0) <> &H68 Then MoveMemory ByVal VarPtr(OriginBytes(0)), ByVal pFunc, 6 p = GetPtr(AddressOf MyDialogBoxParam) HookBytes(0) = &H68 MoveMemory ByVal VarPtr(HookBytes(1)), ByVal VarPtr(p), 4 HookBytes(5) = &HC3 MoveMemory ByVal pFunc, ByVal VarPtr(HookBytes(0)), 6 Flag = True Hook = True End If End If End Function Private Function MyDialogBoxParam(ByVal hInstance As LongPtr, _ ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, _ ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer If pTemplateName = 4070 Then MyDialogBoxParam = 1 Else RecoverBytes MyDialogBoxParam = DialogBoxParam(hInstance, pTemplateName, _ hWndParent, lpDialogFunc, dwInitParam) Hook End If End Function
  • Paste this code in Module2 and run it Sub unprotected() If Hook Then MsgBox "VBA Project is unprotected!", vbInformation, "*****" End If End Sub

DISCLAIMER This worked for me and I have documented it here in the hope it will help someone out. I have not fully tested it. Please be sure to save all open files before proceeding with this option.

I used this code to remove VBA project password from one of my very old excel add-in file (.xla) and it worked very well. Thanks a lot!

Works great, thanks! ;) For others, be sure not to read too quickly and miss the step "and run it", lol. :P (I didn't have my coffee yet, so give me a break lol)

Shame - but this causes Excel 2016 64 bit to crash when I try it.

Not working, I get an out of memory message.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 92

I've built upon c Thanh Nguyn's fantastic answer to allow this method to work with 64-bit versions of Excel. I'm running Excel 2010 64-Bit on 64-Bit Windows 7.

  • Open the file(s) that contain your locked VBA Projects.
  • Create a new xlsm file and store this code in Module1 Option Explicit Private Const PAGE_EXECUTE_READWRITE = &H40 Private Declare PtrSafe Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As LongPtr, Source As LongPtr, ByVal Length As LongPtr) Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (lpAddress As LongPtr, _ ByVal dwSize As LongPtr, ByVal flNewProtect As LongPtr, lpflOldProtect As LongPtr) As LongPtr Private Declare PtrSafe Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As LongPtr Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, _ ByVal lpProcName As String) As LongPtr Private Declare PtrSafe Function DialogBoxParam Lib "user32" Alias "DialogBoxParamA" (ByVal hInstance As LongPtr, _ ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, _ ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer Dim HookBytes(0 To 5) As Byte Dim OriginBytes(0 To 5) As Byte Dim pFunc As LongPtr Dim Flag As Boolean Private Function GetPtr(ByVal Value As LongPtr) As LongPtr GetPtr = Value End Function Public Sub RecoverBytes() If Flag Then MoveMemory ByVal pFunc, ByVal VarPtr(OriginBytes(0)), 6 End Sub Public Function Hook() As Boolean Dim TmpBytes(0 To 5) As Byte Dim p As LongPtr Dim OriginProtect As LongPtr Hook = False pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "DialogBoxParamA") If VirtualProtect(ByVal pFunc, 6, PAGE_EXECUTE_READWRITE, OriginProtect) <> 0 Then MoveMemory ByVal VarPtr(TmpBytes(0)), ByVal pFunc, 6 If TmpBytes(0) <> &H68 Then MoveMemory ByVal VarPtr(OriginBytes(0)), ByVal pFunc, 6 p = GetPtr(AddressOf MyDialogBoxParam) HookBytes(0) = &H68 MoveMemory ByVal VarPtr(HookBytes(1)), ByVal VarPtr(p), 4 HookBytes(5) = &HC3 MoveMemory ByVal pFunc, ByVal VarPtr(HookBytes(0)), 6 Flag = True Hook = True End If End If End Function Private Function MyDialogBoxParam(ByVal hInstance As LongPtr, _ ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, _ ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer If pTemplateName = 4070 Then MyDialogBoxParam = 1 Else RecoverBytes MyDialogBoxParam = DialogBoxParam(hInstance, pTemplateName, _ hWndParent, lpDialogFunc, dwInitParam) Hook End If End Function
  • Paste this code in Module2 and run it Sub unprotected() If Hook Then MsgBox "VBA Project is unprotected!", vbInformation, "*****" End If End Sub

DISCLAIMER This worked for me and I have documented it here in the hope it will help someone out. I have not fully tested it. Please be sure to save all open files before proceeding with this option.

I used this code to remove VBA project password from one of my very old excel add-in file (.xla) and it worked very well. Thanks a lot!

I tried c Thanh Nguyn's solution and got an "update for 64-bit" message. I was panicking until I saw your update. Worked like a charm, thank you!

Works great, thanks! ;) For others, be sure not to read too quickly and miss the step "and run it", lol. :P (I didn't have my coffee yet, so give me a break lol)

Shame - but this causes Excel 2016 64 bit to crash when I try it.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 54

Named ranges ensure that your macros do not break when (not if!) the c...

One small point of emphasis I'll add to all the excellent answers given above:

Probably the biggest thing you can do to avoid using Select is to as much as possible, use named ranges (combined with meaningful variable names) in your VBA code. This point was mentioned above, but glossed over a bit; however, it deserves special attention.

Here are a couple additional reasons to make liberal use of named ranges though I am sure I could think of more.

Dim Months As Range
Dim MonthlySales As Range

Set Months = Range("Months")
'e.g, "Months" might be a named range referring to A1:A12

Set MonthlySales = Range("MonthlySales")
'e.g, "Monthly Sales" might be a named range referring to B1:B12

Dim Month As Range
For Each Month in Months
    Debug.Print MonthlySales(Month.Row)
Next Month

It is pretty obvious what the named ranges Months and MonthlySales contain, and what the procedure is doing.

Why is this important? Partially because it is easier for other people to understand it, but even if you are the only person who will ever see or use your code, you should still use named ranges and good variable names because YOU WILL FORGET what you meant to do with it a year later, and you will waste 30 minutes just figuring out what your code is doing.

Consider, if the above example had been written like this:

Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Range("A1:A12")
Set rng2 = Range("B1:B12")

Dim rng3 As Range
For Each rng3 in rng1 
    Debug.Print rng2(rng3.Row)
Next rng3

This code will work just fine at first - that is until you or a future user decides "gee wiz, I think I'm going to add a new column with the year in Column A!", or put an expenses column between the months and sales columns, or add a header to each column. Now, your code is broken. And because you used terrible variable names, it will take you a lot more time to figure out how to fix it than it should take.

If you had used named ranges to begin with, the Months and Sales columns could be moved around all you like, and your code will continue working just fine.

The debate about whether named ranges are good or bad spreadsheet design continues - I'm firmly in the no camp. In my experience they increase errors (for standard users who have no need of code).

I agree with your development philosophy; however I think the paper is nonsense. It talks about how range names can confuse novices who are debugging spreadsheets, but anyone who uses novices to look at complex spreadsheets gets what they deserve! I used to work for a firm who reviewed financial spreadsheets, and I can tell you that it is not the sort of job you give to a novice.

There is no meaningful debate. Anyone who argues against defined names has not taken the time to fully understand their ramifications. Named formulas may be the single most profound and useful construct in all of Excel.

@brettdj: Your citation is correct, but you forgot to mention that it is followed by six "Except..." phrases. One of them being: "Except as a substitute for cell references in macro coding Always use Excel Names as a substitute for cell references when constructing macros. This is to avoid errors arising from the insertion of additional rows or columns whereby the macro coding no longer points to the intended source data."

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 52

Named ranges ensure that your macros do not break when (not if!) the c...

One small point of emphasis I'll add to all the excellent answers given above:

Probably the biggest thing you can do to avoid using Select is to as much as possible, use named ranges (combined with meaningful variable names) in your VBA code. This point was mentioned above, but glossed over a bit; however, it deserves special attention.

Here are a couple additional reasons to make liberal use of named ranges though I am sure I could think of more.

Dim Months As Range
Dim MonthlySales As Range

Set Months = Range("Months")
'e.g, "Months" might be a named range referring to A1:A12

Set MonthlySales = Range("MonthlySales")
'e.g, "Monthly Sales" might be a named range referring to B1:B12

Dim Month As Range
For Each Month in Months
    Debug.Print MonthlySales(Month.Row)
Next Month

It is pretty obvious what the named ranges Months and MonthlySales contain, and what the procedure is doing.

Why is this important? Partially because it is easier for other people to understand it, but even if you are the only person who will ever see or use your code, you should still use named ranges and good variable names because YOU WILL FORGET what you meant to do with it a year later, and you will waste 30 minutes just figuring out what your code is doing.

Consider, if the above example had been written like this:

Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Range("A1:A12")
Set rng2 = Range("B1:B12")

Dim rng3 As Range
For Each rng3 in rng1 
    Debug.Print rng2(rng3.Row)
Next rng3

This code will work just fine at first - that is until you or a future user decides "gee wiz, I think I'm going to add a new column with the year in Column A!", or put an expenses column between the months and sales columns, or add a header to each column. Now, your code is broken. And because you used terrible variable names, it will take you a lot more time to figure out how to fix it than it should take.

If you had used named ranges to begin with, the Months and Sales columns could be moved around all you like, and your code will continue working just fine.

The debate about whether named ranges are good or bad spreadsheet design continues - I'm firmly in the no camp. In my experience they increase errors (for standard users who have no need of code).

I agree with your development philosophy; however I think the paper is nonsense. It talks about how range names can confuse novices who are debugging spreadsheets, but anyone who uses novices to look at complex spreadsheets gets what they deserve! I used to work for a firm who reviewed financial spreadsheets, and I can tell you that it is not the sort of job you give to a novice.

There is no meaningful debate. Anyone who argues against defined names has not taken the time to fully understand their ramifications. Named formulas may be the single most profound and useful construct in all of Excel.

@brettdj: Your citation is correct, but you forgot to mention that it is followed by six "Except..." phrases. One of them being: "Except as a substitute for cell references in macro coding Always use Excel Names as a substitute for cell references when constructing macros. This is to avoid errors arising from the insertion of additional rows or columns whereby the macro coding no longer points to the intended source data."

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba
Rectangle 27 56

Colin Pickard has an excellent answer, but there is one 'watch out' with this. There are instances (I haven't figured out the cause yet) where the total length of the "CMG=........GC=...." entry in the file is different from one excel file to the next. In some cases, this entry will be 137 bytes, and in others it will be 143 bytes. The 137 byte length is the odd one, and if this happens when you create your file with the '1234' password, just create another file, and it should jump to the 143 byte length.

If you try to paste the wrong number of bytes into the file, you will lose your VBA project when you try to open the file with Excel.

This is not valid for Excel 2007/2010 files. The standard .xlsx file format is actually a .zip file containing numerous sub-folders with the formatting, layout, content, etc, stored as xml data. For an unprotected Excel 2007 file, you can just change the .xlsx extension to .zip, then open the zip file and look through all the xml data. It's very straightforward.

However, when you password protect an Excel 2007 file, the entire .zip (.xlsx) file is actually encrypted using RSA encryption. It is no longer possible to change the extension to .zip and browse the file contents.

Then you need to use standard zip hacking tools. Its no longer a "how do i back an excel file" problem.

@Anonymous Type: I think a zip cracking tool won't help. As I understand Stewbob, it's not the file entries in the zip file that are encrypted, but the whole zip file itself, which should include the header and the central directory.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 56

Colin Pickard has an excellent answer, but there is one 'watch out' with this. There are instances (I haven't figured out the cause yet) where the total length of the "CMG=........GC=...." entry in the file is different from one excel file to the next. In some cases, this entry will be 137 bytes, and in others it will be 143 bytes. The 137 byte length is the odd one, and if this happens when you create your file with the '1234' password, just create another file, and it should jump to the 143 byte length.

If you try to paste the wrong number of bytes into the file, you will lose your VBA project when you try to open the file with Excel.

This is not valid for Excel 2007/2010 files. The standard .xlsx file format is actually a .zip file containing numerous sub-folders with the formatting, layout, content, etc, stored as xml data. For an unprotected Excel 2007 file, you can just change the .xlsx extension to .zip, then open the zip file and look through all the xml data. It's very straightforward.

However, when you password protect an Excel 2007 file, the entire .zip (.xlsx) file is actually encrypted using RSA encryption. It is no longer possible to change the extension to .zip and browse the file contents.

Then you need to use standard zip hacking tools. Its no longer a "how do i back an excel file" problem.

@Anonymous Type: I think a zip cracking tool won't help. As I understand Stewbob, it's not the file entries in the zip file that are encrypted, but the whole zip file itself, which should include the header and the central directory.

How about when the file you want to get into has the shorter keys? Just keep creating vba docs until you get one that has 137?

Are you sure that the entire zipfile is encrypted when you lock the VBA project? I can still open the zipfile and see the file structure.... And subfolder xl\ contains the file vbaProject.bin which has the familiar "CMG=... GC=" hashing block.

Is there a way to crack the password on an Excel VBA Project? - Stack ...

excel excel-vba passwords
Rectangle 27 23

You'll find an MD5 and SHA256 implementation for VB and VBScript here.

However someone has already done that. Unfortunately the solution is at experts-exchange which doesn't allow straight links. So we have to go through Google. Click here to perform a Google search and then click the first result. Scroll down a lot to see the accepted solution.

Exactly what I was looking for, thanks! The SHA256 class module can be imported directly into Excel VBA unchanged.

Latest versions available on the frez site at: frez.co.uk/vb6.aspx

Password hash function for Excel VBA - Stack Overflow

excel excel-vba hash sha1
Rectangle 27 30

I'm going to give the short answer since everyone else gave the long one.

You'll get .select and .activate whenever you record macros and reuse them. When you .select a cell or sheet it just makes it active. From that point on whenever you use unqualified references like Range.Value they just use the active cell and sheet. This can also be problematic if you don't watch where your code is placed or a user clicks on the workbook.

So, you can elinate these issues by directly referencing your cells. Which goes:

'create and set a range
Dim Rng As Excel.Range
Set Rng = Workbooks("Book1").Worksheets("Sheet1").Range("A1")
'OR
Set Rng = Workbooks(1).Worksheets(1).Cells(1, 1)
'Just deal with the cell directly rather than creating a range
'I want to put the string "Hello" in Range A1 of sheet 1
Workbooks("Book1").Worksheets("Sheet1").Range("A1").value = "Hello"
'OR
Workbooks(1).Worksheets(1).Cells(1, 1).value = "Hello"

There are various combinations of these methods, but that would be the general idea expressed as shortly as possible for impatient people like me.

How to avoid using Select in Excel VBA - Stack Overflow

excel excel-vba