如何对vbaproject新添加的工作簿进行密码保护并关闭工作簿

问题描述 投票:0回答:1

我想用密码保护vbaproject新添加的工作簿并关闭工作簿。我正在使用 Excel 2021。

我找到了如何以编程方式设置密码 vba 项目的代码。

方法1:https://www.mrexcel.com/board/threads/lock-unlock-vbaprojects-programmatically-without-sendkeys.1136415/page-3#post-6223568

Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0& To 7&) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hMod As LongPtr, ByVal dwThreadId As Long) As LongPtr
    Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long
    Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
    Private Declare PtrSafe Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr
    Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
    Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) 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
    Private Declare PtrSafe Function SendMessageByString Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As String) As Long
    Private Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
    Private Declare PtrSafe Function SetWindowPos Lib "user32" (ByVal hwnd As LongPtr, ByVal hWndInsertAfter As LongPtr, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function SetActiveWindow Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Sub SetLastError Lib "Kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare PtrSafe Function DispCallFunc Lib "OleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, ByRef lpiid As Any) As Long
    Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As Any) As Long
#Else
    Private Enum LongPtr
        [_]
    End Enum
    Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hMod As LongPtr, ByVal dwThreadId As Long) As LongPtr
    Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hhk As LongPtr) As Long
    Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
    Private Declare Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr
    Private Declare Function GetActiveWindow Lib "user32" () As LongPtr
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
    Private Declare 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 Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
    Private Declare Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
    Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As LongPtr, ByVal hWndInsertAfter As LongPtr, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
    Private Declare Function SetActiveWindow Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare Sub SetLastError Lib "Kernel32.dll" (ByVal dwErrCode As Long)
    Private Declare Function DispCallFunc Lib "OleAut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, ByRef lpiid As Any) As Long
    Private Declare Function CoCreateInstance Lib "ole32" (ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As LongPtr) As Long
    Private Declare Function CLSIDFromString Lib "ole32" (ByVal OleStringCLSID As LongPtr, ByRef cGUID As Any) As Long
#End If

Private lHook As LongPtr, oProject As Object, sWinClassName As String, _
        sWorkbookName As String, sPassword As String, bNotify As Boolean, bHooked As Boolean


Public Property Let LockVBProject( _
    ByVal WorkbookName As String, _
    ByVal Password As String, _
    Optional ByVal NotifyUser As Boolean, _
    ByVal bLock As Boolean _
)
    Const WH_CBT = 5&
    Dim hwnd As LongPtr
 
    If bHooked Then Exit Property
 
    hwnd = GetActiveWindow: bNotify = NotifyUser:  bHooked = True
 
    On Error GoTo ErrHandler
    With Application.VBE
        Set .ActiveVBProject = Application.Workbooks(WorkbookName).VBProject
        Set oProject = .ActiveVBProject
        If bLock Then
            If .ActiveVBProject.Protection = 0 Then
                sWinClassName = "VBAProject - Project Properties"
                sWorkbookName = WorkbookName
            Else
                MsgBox "VBProect already locked.": Exit Property
            End If
        Else
            If .ActiveVBProject.Protection Then
                sWinClassName = "VBAProject Password"
            Else
                MsgBox "VBProect already unlocked.": Exit Property
            End If
        End If
    End With
    sPassword = Password
    lHook = SetWindowsHookEx(WH_CBT, AddressOf Catch_DlgBox_Activation, 0, GetCurrentThreadId)
    Application.VBE.CommandBars(1&).FindControl(ID:=2578, recursive:=True).Execute
    bHooked = False
Exit Property

ErrHandler:
    Call UnHook
    bHooked = False
    MuteSpeakers False
    If Err.Number = 9 Then
        MsgBox "Error : " & Err.Number & vbNewLine & vbNewLine & "Invalid Project Name.", vbExclamation
    Else
        MsgBox "Runtime Error : " & Err.Number & vbCr & vbCr & Err.Description, vbExclamation
    End If
End Property

Private Function Catch_DlgBox_Activation( _
    ByVal idHook As Long, _
    ByVal wParam As LongPtr, _
    ByVal lParam As LongPtr _
) As LongPtr
  
    Const HCBT_ACTIVATE = 5, SWP_HIDEWINDOW = &H80
    Dim sBuff As String * 256, lRet As Long
    Dim hwnd As LongPtr

    If idHook = HCBT_ACTIVATE Then
        lRet = GetClassName(wParam, sBuff, 256&)
        If Left(sBuff, lRet) = "#32770" Then
            sBuff = ""
            lRet = GetWindowText(wParam, sBuff, 256&)
            If Left(sBuff, lRet) = sWinClassName Then
                Call UnHook
                SetWindowPos wParam, 0, 0&, 0&, 0&, 0&, SWP_HIDEWINDOW
                Call SetTimer(Application.hwnd, wParam, 0, AddressOf Protect_UnProtect_Routine)
            End If
        End If
    End If
    Catch_DlgBox_Activation = CallNextHookEx(lHook, idHook, ByVal wParam, ByVal lParam)
 
End Function

Private Sub UnHook()
    Call UnhookWindowsHookEx(lHook)
End Sub

Private Sub UnHook2()
    Call UnHook
    MuteSpeakers False
    If bNotify Then
        If IsProjectUnLocked Then
            MsgBox "Project successfully unlocked!", vbInformation
        Else
            MsgBox "Failed to unlock the VBProject!" & vbNewLine & vbNewLine & _
                   "Passwords are Case-Sensitive ... Make sure the provided Password is correct.", vbExclamation
        End If
    End If
End Sub

Private Function IsProjectUnLocked() As Boolean
    IsProjectUnLocked = CBool(oProject.Protection = 0)
End Function

Private Sub Protect_UnProtect_Routine( _
    ByVal hwnd As LongPtr, _
    ByVal uMsg As Long, _
    ByVal nIDEvent As LongPtr, _
    ByVal dwTimer As Long _
)
    Const TCM_FIRST = &H1300
    Const TCM_SETCURSEL = (TCM_FIRST + 12&)
    Const TCM_SETCURFOCUS = (TCM_FIRST + 48&)
    Const EM_SETMODIFY = &HB9
    Const BM_SETCHECK = &HF1
    Const BST_CHECKED = &H1
    Const BM_GETCHECK = &HF0
    Const BM_CLICK = &HF5
    Const WM_SETTEXT = &HC
    Const WH_CBT = 5&
    Const GW_CHILD = 5&
    Dim hCurrentDlg As LongPtr, hwndSysTab As LongPtr
    Dim sBuff As String * 256, lRet As Long
 
    On Error GoTo ErrHandler
    Call KillTimer(Application.hwnd, nIDEvent)
    hCurrentDlg = nIDEvent
    If sWinClassName = "VBAProject - Project Properties" Then
        hwndSysTab = FindWindowEx(hCurrentDlg, 0, "SysTabControl32", vbNullString)
        Call SendMessage(hwndSysTab, TCM_SETCURFOCUS, 1, ByVal 0&)
        Call SendMessage(hwndSysTab, TCM_SETCURSEL, 1, ByVal 0&)
        Call SendMessage(GetDlgItem(GetNextWindow(hCurrentDlg, GW_CHILD), &H1557), BM_SETCHECK, BST_CHECKED, ByVal 0&)
        Call SendMessageByString(GetDlgItem(GetNextWindow(hCurrentDlg, GW_CHILD), &H1555), WM_SETTEXT, 0, sPassword)
        Call SendMessage(GetDlgItem(GetNextWindow(hCurrentDlg, GW_CHILD), &H1555), EM_SETMODIFY, True, ByVal 0&)
        Call SendMessageByString(GetDlgItem(GetNextWindow(hCurrentDlg, GW_CHILD), &H1556), WM_SETTEXT, 0, sPassword)
        Call SendMessage(GetDlgItem(GetNextWindow(hCurrentDlg, GW_CHILD), &H1556), EM_SETMODIFY, True, ByVal 0&)
        Call SendMessage(GetDlgItem(hCurrentDlg, &H1), BM_CLICK, 0, ByVal 0&)
        Call Application.OnTime(Now, "SaveVBProjectChanges")
    ElseIf sWinClassName = "VBAProject Password" Then
        MuteSpeakers True 'temporarly mute the system sounds\speakers to avoid awful sound when running Catch_DlgBox_Creation
        If SendMessageByString(GetDlgItem(hCurrentDlg, &H155E), WM_SETTEXT, 0, sPassword) Then
            Call SendMessage(GetDlgItem(hCurrentDlg, &H155E), EM_SETMODIFY, True, ByVal 0&)
            lHook = SetWindowsHookEx(WH_CBT, AddressOf Catch_DlgBox_Creation, 0, GetCurrentThreadId)
            lRet = GetWindowText(GetActiveWindow, sBuff, 256&)
            If Left(sBuff, lRet) = "VBAProject Password" Then
                Call SendMessage(GetDlgItem(hCurrentDlg, &H1), BM_CLICK, 0, ByVal 0&)
                Call SendMessage(GetDlgItem(GetActiveWindow, &H2), BM_CLICK, 0, ByVal 0&)
            Else
                Call SendMessage(GetDlgItem(hCurrentDlg, &H1), BM_CLICK, 0, ByVal 0&)
            End If
            Call Application.OnTime(Now + TimeSerial(0, 0, 3), "UnHook2")  'wait 3 secs before unmuting the speakers.
        End If
    End If
    Exit Sub
ErrHandler:
    Call UnHook2
    MsgBox "Runtime Error : " & Err.Number & vbCr & vbCr & Err.Description, vbExclamation
End Sub

Private Function Catch_DlgBox_Creation( _
    ByVal idHook As Long, _
    ByVal wParam As LongPtr, _
    ByVal lParam As LongPtr _
) As LongPtr

    Const HCBT_CREATEWND = 3&
    Dim sBuff As String * 256, lRet As Long
 
    If idHook = HCBT_CREATEWND Then
        lRet = GetClassName(wParam, sBuff, 256)
        If Left(sBuff, lRet) = "#32770" Then
            Catch_DlgBox_Creation = -1
            Exit Function
        End If
    End If
    Catch_DlgBox_Creation = CallNextHookEx(lHook, idHook, ByVal wParam, ByVal lParam)
End Function

Private Sub SaveVBProjectChanges()
    On Error Resume Next
    Application.EnableEvents = False
        Workbooks(sWorkbookName).Save
    Application.EnableEvents = True
End Sub

Private Sub MuteSpeakers(Optional ByVal bMute As Boolean = True)

    Const CLSID_MMDeviceEnumerator = "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
    Const IID_IMMDeviceEnumerator = "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
    Const IID_IAudioEndpointVolume = "{5CDF2C82-841E-4546-9722-0CF74078229A}"
    Const IID_NULL = "{00000000-0000-0000-0000-000000000000}"
    Const CLSCTX_INPROC_SERVER = 1&
    Const CC_STDCALL = 4&
 
    #If Win64 Then
        Const NULL_PTR = 0^
        Const PTR_SIZE = 8&
    #Else
        Const NULL_PTR = 0&
        Const PTR_SIZE = 4&
    #End If
 
    Dim tClsID As GUID, tIID As GUID
    Dim pDeviceEnumerator As LongPtr, pdefaultDevice As LongPtr, pIAudioEndpointVolume As LongPtr
    Dim eRender As Long, eMultimedia As Long
    Dim lRet As Long

    lRet = CLSIDFromString(StrPtr(CLSID_MMDeviceEnumerator), tClsID)
    lRet = IIDFromString(StrPtr(IID_IMMDeviceEnumerator), tIID)

    'Create an enumerator for the audio endpoint devices
    lRet = CoCreateInstance(tClsID, NULL_PTR, CLSCTX_INPROC_SERVER, tIID, pDeviceEnumerator)
    If lRet Then MsgBox "Failed to get IMMDeviceEnumerator.": Exit Sub
 
    eRender = 0&: eMultimedia = 1&
    'IMMDeviceEnumerator::GetDefaultAudioEndpoint Method.
    lRet = vtblCall(pDeviceEnumerator, 4& * PTR_SIZE, vbLong, CC_STDCALL, eRender, eMultimedia, VarPtr(pdefaultDevice))
    If lRet Then MsgBox "Failed to get IMMDevice.": Exit Sub
 
    lRet = IIDFromString(StrPtr(IID_IAudioEndpointVolume), tIID)
    'IMMDevice::Activate Method.
    lRet = vtblCall(pdefaultDevice, 3& * PTR_SIZE, vbLong, CC_STDCALL, VarPtr(tIID), CLSCTX_INPROC_SERVER, 0&, VarPtr(pIAudioEndpointVolume))
    If lRet Then MsgBox "Failed to get IAudioEndpointVolume.": Exit Sub

    lRet = IIDFromString(StrPtr(IID_NULL), tIID)
    lRet = vtblCall(pIAudioEndpointVolume, 14& * PTR_SIZE, vbLong, CC_STDCALL, CLng(Abs(bMute)), VarPtr(tIID))
 
    'Release Interfaces.
    lRet = vtblCall(pIAudioEndpointVolume, 2& * PTR_SIZE, vbLong, CC_STDCALL)
    lRet = vtblCall(pdefaultDevice, 2& * PTR_SIZE, vbLong, CC_STDCALL)
    lRet = vtblCall(pDeviceEnumerator, 2& * PTR_SIZE, vbLong, CC_STDCALL)

End Sub


Private Function vtblCall( _
    ByVal InterfacePointer As LongPtr, _
    ByVal VTableOffset As Long, _
    ByVal FunctionReturnType As Long, _
    ByVal CallConvention As Long, _
    ParamArray FunctionParameters() As Variant _
) As Variant

    Dim vParamPtr() As LongPtr

    If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
    If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function

    Dim pIndex As Long, pCount As Long
    Dim vParamType() As Integer
    Dim vRtn As Variant, vParams() As Variant

    vParams() = FunctionParameters()
    pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
    If pCount = 0& Then
        ReDim vParamPtr(0& To 0&)
        ReDim vParamType(0& To 0&)
    Else
        ReDim vParamPtr(0& To pCount - 1&)
        ReDim vParamType(0& To pCount - 1&)
        For pIndex = 0& To pCount - 1&
            vParamPtr(pIndex) = VarPtr(vParams(pIndex))
            vParamType(pIndex) = VarType(vParams(pIndex))
        Next
    End If

    pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, FunctionReturnType, pCount, _
    vParamType(0&), vParamPtr(0&), vRtn)
    If pIndex = 0& Then
        vtblCall = vRtn
    Else
        SetLastError pIndex
    End If
End Function

方法2:VBA:如何使用代码对模块进行密码保护?

Sub ProtectVBProj(ByRef WB As Workbook, ByVal Pwd As String)

    Dim vbProj As Object

    Set vbProj = WB.VBProject

    If vbProj.Protection = 1 Then Exit Sub ' already protected

    Set Application.VBE.ActiveVBProject = vbProj

    SendKeys "%TE+{TAB}{RIGHT}%V%P" & Pwd & "%C" & Pwd & "{TAB}{ENTER}"

End Sub

两种方法都可以在以下代码中正常工作,无需关闭工作簿,即没有行 xWB.Close savechanges:=True:

如果没有关闭工作簿的行,密码将保存在新添加的工作簿中。现有工作簿中不会保存密码。

但是,当该行关闭工作簿时,密码将不会保存在新添加的工作簿中。密码将保存在现有工作簿中。

Public Sub exportWB()

    Const sFFPath As String = "D:\A.xlsm"
    Dim xWB As Workbook
    Set xWB = Workbooks.Add
    'LockVBProject(WorkbookName:=xWB.name, Password:="1234") = True
    ProtectVBProj xWB, "1234" 
    xWB.SaveAs Filename:=sFFPath, _
                            FileFormat:=xlOpenXMLWorkbookMacroEnabled, _
                            ConflictResolution:=Excel.XlSaveConflictResolution.xlLocalSessionChanges
    xWB.Close savechanges:=True 'The line with problem
    
End Sub

有办法解决吗?预先感谢。

excel vba
1个回答
0
投票

感谢 Jaafar Tribak,解决方法是使用 Application.OnTime,如下所示。谢谢贾法尔·特里巴克。

https://www.mrexcel.com/board/threads/lock-unlock-vbaprojects-programmatically-without-sendkeys.1136415/post-6223735

Sub exportWB()
    Const sFFPath As String = "D:\A.xlsm"
    Dim xWB As Workbook
    Set xWB = Workbooks.Add
    LockVBProject(WorkbookName:=xWB.Name, Password:="password") = True
    xWB.VBProject.VBComponents.Add (1) 'vbext_ct_StdModule
    xWB.SaveAs Filename:=sFFPath, _
    FileFormat:=xlOpenXMLWorkbookMacroEnabled, _
    ConflictResolution:=Excel.XlSaveConflictResolution.xlLocalSessionChanges
    Application.OnTime Now, "'SaveNow """ & xWB.Name & "'"
    Set xWB = Nothing
End Sub

Sub SaveNow(ByVal sWbkName As String)
    Workbooks(sWbkName).Close savechanges:=True
End Sub
© www.soinside.com 2019 - 2024. All rights reserved.