Private/COMInterop.psm1
|
using namespace System # --------------------------------------------------------------------------- # COMInterop – Pure PowerShell COM wrappers for Windows IAttachmentExecute API # --------------------------------------------------------------------------- # Use Add-Type to define the IAttachmentExecute interface for reliable COM calls. # We use [PreserveSig] for methods like Save() so we can manually handle HRESULTs. $attachmentTypeDefinition = @' using System; using System.Runtime.InteropServices; namespace AntiVirus.Interop { [ComImport] [Guid("73db1241-1e85-4581-8e4f-a81e1d0f8c57")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IAttachmentExecute { void SetClientGuid(ref Guid guid); void SetClientTitle([MarshalAs(UnmanagedType.LPWStr)] string title); void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string fileName); void SetLocalPath([MarshalAs(UnmanagedType.LPWStr)] string localPath); void SetEditMode(bool editMode); void SetSource([MarshalAs(UnmanagedType.LPWStr)] string source); void SetReferrer([MarshalAs(UnmanagedType.LPWStr)] string referrer); void CheckPolicy(); [PreserveSig] int Prompt(IntPtr hwnd, uint prompt, out uint action); [PreserveSig] int Save(); [PreserveSig] int Execute(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)] string verb, out IntPtr process); [PreserveSig] int SaveWithProgress(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)] string verb, out IntPtr process); void ClearClientState(); } public static class AttachmentExecuteHelper { public static int Scan(object comObject, Guid clientGuid, string path) { IAttachmentExecute atsvc = (IAttachmentExecute)comObject; atsvc.SetClientGuid(ref clientGuid); atsvc.SetFileName(path); atsvc.SetLocalPath(path); atsvc.SetSource("about:internet"); return atsvc.Save(); } public static void Clear(object comObject) { try { IAttachmentExecute atsvc = (IAttachmentExecute)comObject; atsvc.ClearClientState(); } catch {} } } } '@ try { Add-Type -TypeDefinition $attachmentTypeDefinition -ErrorAction SilentlyContinue } catch { # Type may already be defined in the session. $null } # Helper class that wraps the IAttachmentExecute COM API class AttachmentScannerCOM { hidden static [Guid] $CLSID_AttachmentServices = [Guid]::new("4125dd96-e03a-4103-8f70-e0597d803b9c") # Create an instance of the COM AttachmentServices CoClass. static [object] CreateAttachmentExecute() { $comType = [Type]::GetTypeFromCLSID([AttachmentScannerCOM]::CLSID_AttachmentServices) if ($null -eq $comType) { throw [System.InvalidOperationException]::new( "IAttachmentExecute COM class not registered on this system. " + "This API is available on Windows 7+ with an antivirus product installed." ) } return [System.Activator]::CreateInstance($comType) } ## Searches loaded assemblies for a type by name. hidden static [type] _FindType([string]$fullName) { foreach ($asm in [AppDomain]::CurrentDomain.GetAssemblies()) { $t = $asm.GetType($fullName) if ($null -ne $t) { return $t } } return $null } # Scan a file using the IAttachmentExecute COM API. # Returns the raw HRESULT as a signed int. static [int] ScanFile([Guid]$clientGuid, [string]$path) { $rawObj = [AttachmentScannerCOM]::CreateAttachmentExecute() # Use runtime type lookup to avoid parse-time errors in PowerShell classes. $helperType = [AttachmentScannerCOM]::_FindType("AntiVirus.Interop.AttachmentExecuteHelper") if ($null -eq $helperType) { throw [InvalidOperationException]::new("Could not find AntiVirus.Interop.AttachmentExecuteHelper. Ensure Add-Type succeeded.") } try { # Orchestrate the call via the C# helper for reliable IUnknown access. [int]$hresult = $helperType::Scan($rawObj, $clientGuid, $path) return $hresult } catch { throw [System.InvalidOperationException]::new( "Failed to perform AV scan via IAttachmentExecute: $($_.Exception.Message)", $_.Exception ) } finally { try { $helperType::Clear($rawObj) } catch { $null } [System.Runtime.InteropServices.Marshal]::ReleaseComObject($rawObj) | Out-Null } } } # These classes are kept if needed for type reference, though the C# interface # defined above is what handles the actual work. class IAttachmentExecute { [string]$FileName [string]$Source [string]$ClientTitle [guid]$ClientGuid [string]$LocalPath [string]$Referrer [void] Save() {} [void] Execute($Process) {} [void] ClearClientState() {} } |