owasp-modsecurity/ModSecurity

Event message with description "Invalid function" in the Windows Application Event Log

Opened this issue · 13 comments

Describe the bug

Confused ModSecurity event message in the Windows Application Event Log.

To Reproduce

  1. Install ModSecurityIIS_2.9.7-64b-64.msi
  2. Configure ModSecurity for the website
  3. Open website in a browser
  4. Check events in the Windows Application Event Log

Actual result

Event message in the Windows Application Event Log:

Log Name: Application Source: ModSecurity Date: 6/23/2025 5:54:11 AM Event ID: 1 Task Category: None Level: Information Keywords: Classic User: N/A Computer: EC2AMAZ-UJ76N58 Description: Incorrect function. Event Xml: <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="ModSecurity" /> <EventID Qualifiers="0">1</EventID> <Version>0</Version> <Level>4</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x80000000000000</Keywords> <TimeCreated SystemTime="2025-06-23T05:54:11.1744601Z" /> <EventRecordID>42355</EventRecordID> <Correlation /> <Execution ProcessID="0" ThreadID="0" /> <Channel>Application</Channel> <Computer>EC2AMAZ-UJ76N58</Computer> <Security /> </System> <EventData> <Data>ModSecurity for IIS (STABLE)/2.9.7 (http://www.modsecurity.org/) configured.</Data> </EventData> </Event>

Expected behavior

Event message in the Windows Application Event Log:

Log Name: Application Source: ModSecurity Date: 6/23/2025 5:54:11 AM Event ID: 1 Task Category: None Level: Information Keywords: Classic User: N/A Computer: EC2AMAZ-UJ76N58 Description: ModSecurity for IIS (STABLE)/2.9.7 (http://www.modsecurity.org/) configured. Event Xml: <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="ModSecurity" /> <EventID Qualifiers="0">1</EventID> <Version>0</Version> <Level>4</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x80000000000000</Keywords> <TimeCreated SystemTime="2025-06-23T05:54:11.1744601Z" /> <EventRecordID>42355</EventRecordID> <Correlation /> <Execution ProcessID="0" ThreadID="0" /> <Channel>Application</Channel> <Computer>EC2AMAZ-UJ76N58</Computer> <Security /> </System> <EventData> <Data>ModSecurity for IIS (STABLE)/2.9.7 (http://www.modsecurity.org/) configured.</Data> </EventData> </Event>

Server (please complete the following information):

  • ModSecurity version (and connector): 2.9.7 MSI installer
  • WebServer: IIS
  • OS (and distro): Windows

Hi @skvoboo-gh,

thanks for reporting this issue. As you remember there was an issue with Windows module source code (#3399 - that was also reported by you), so first of all could you check which version you use? I mean the source version (perhaps 2.9.10?).

There was an issue (#3373) which was fixed in #3374 - there the logging mechanism was changed (more precisely: there was a change in logging mechanism in #3192, which was reported in #3373 and fixed in #3374).

May be we should apply this modification (#3374) for Windows too.

I am using ModSecurity 2.9.7, because this is a latest binary distribution (with MSI installer in assets).
I don't think all the issues you listed above are related to this issue.
I think the root cause of this issue is the lack of registration of the ModSecurity event source in the Windows registry.

An application can use the Application log without adding a new event source to the registry. If the application calls RegisterEventSource and passes a source name that cannot be found in the registry, the event-logging service uses the Application log by default. However, because there are no message files, the Event Viewer cannot map any event identifiers or event categories to a description string, and will display an error. For this reason, you should add a unique event source to the registry for your application and specify a message file.

Ah, thanks.

I can try to take a look at that.

FYI, after testing, adding the following minimal message file in the /iis folder resolves the issue:

ModSecurity.mc:

MessageId=0x1
Language=English
%1
.

Compiled using MSVC with the following commands:

mc .\ModSecurity.mc 
rc .\ModSecurity.rc
link -dll -noentry .\ModSecurity.res
-> Produces ModSecurity.dll

Then add the registry entry pointing to the compiled message file:

Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\ModSecurity

EventMessageFile : D:\Path\To\ModSecurity.dll

Test Results:
The event log now properly displays the messages instead of "Incorrect function":

Get-EventLog -LogName Application -Source ModSecurity -Newest 8

   Index Time          EntryType   Source                 InstanceID Message
   ----- ----          ---------   ------                 ---------- -------
  265708 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: StatusEngine call failed. Query: GIX…
  265707 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: StatusEngine call: "2.9.7,IIS,1.7.0/…
  265706 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: LIBXML compiled version="2.9.14"
  265705 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: YAJL compiled version="2.1.0"
  265704 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: LUA compiled version="Lua 5.3"
  265703 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: PCRE compiled version="8.45 "; loade…
  265702 Aug 26 16:41  Information ModSecurity                     1 ModSecurity: APR compiled version="1.7.0"; loaded…
  265701 Aug 26 16:41  Information ModSecurity                     1 ModSecurity for IIS (STABLE)/2.9.7 (http://www.mo…

Hi @A13501350,

FYI, after testing, adding the following minimal message file in the /iis folder resolves the issue:

Would you consider sending a PR?

Do you think it's possible to add a new test pipeline with Windows?

I implemented a CMake-based build system and GitHub Actions CI/CD pipeline for the ModSecurity IIS module on Windows. The CMake setup leverages vcpkg for dependency management, supports both x86 and x64 architectures. The GitHub Actions workflow provides automated packaging using WiX toolset, testing with OWASP Core Ruleset. While the implementation has shown working in initial testing, this pipeline has not undergone comprehensive testing.

1. CMake:

cmake_minimum_required(VERSION 3.15)

project(ModSecurityIIS C CXX)

find_package(LibXml2 CONFIG REQUIRED)
find_package(PCRE2 CONFIG REQUIRED)
find_package(CURL CONFIG REQUIRED)
find_package(APR CONFIG REQUIRED)

# iis/CMakeLists.txt
set(IIS_MODULE_NAME "modsecurityiis")  # Name should match the original output

# Source files for IIS module (reusing Apache sources)
set(IIS_APACHE_SOURCES
    ../apache2/mod_security2.c
    ../apache2/apache2_config.c
    ../apache2/apache2_io.c
    ../apache2/apache2_util.c
    ../apache2/re.c
    ../apache2/re_operators.c
    ../apache2/re_actions.c
    ../apache2/re_tfns.c
    ../apache2/re_variables.c
    ../apache2/msc_logging.c
    ../apache2/msc_xml.c
    ../apache2/msc_multipart.c
    ../apache2/modsecurity.c
    ../apache2/msc_parsers.c
    ../apache2/msc_util.c
    ../apache2/msc_pcre.c
    ../apache2/persist_dbm.c
    ../apache2/msc_reqbody.c
    ../apache2/msc_geo.c
    ../apache2/msc_gsb.c
    ../apache2/msc_crypt.c
    ../apache2/msc_tree.c
    ../apache2/msc_unicode.c
    ../apache2/acmp.c
    ../apache2/msc_lua.c
    ../apache2/msc_release.c
    ../apache2/msc_status_engine.c
    ../apache2/msc_remote_rules.c
    ../apache2/msc_json.c
    ../apache2/libinjection/libinjection_html5.c
    ../apache2/libinjection/libinjection_sqli.c
    ../apache2/libinjection/libinjection_xss.c
)

# Source files for standalone components (if they exist in the project)
set(IIS_STANDALONE_SOURCES
    ../standalone/api.c
    ../standalone/buckets.c
    ../standalone/config.c
    ../standalone/filters.c
    ../standalone/hooks.c
    ../standalone/regex.c
    ../standalone/server.c
)

# Determine architecture
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set(ARCHITECTURE "x64")
else()
    set(ARCHITECTURE "x86")
endif()

# Check if standalone directory exists, if not, exclude those sources
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../standalone)
    set(IIS_STANDALONE_SOURCES "")
endif()

set(IIS_RESOURCE_MC "${CMAKE_CURRENT_SOURCE_DIR}/ModSecurityIISMessage.mc")

set(MC_GENERATED_RC "${CMAKE_CURRENT_BINARY_DIR}/ModSecurityIISMessage.rc")
set(MC_GENERATED_H "${CMAKE_CURRENT_BINARY_DIR}/ModSecurityIISMessage.h")
add_custom_command(
    OUTPUT ${MC_GENERATED_RC} ${MC_GENERATED_H}
    COMMAND mc.exe
    ARGS -U -h "${CMAKE_CURRENT_BINARY_DIR}/" -r "${CMAKE_CURRENT_BINARY_DIR}/" "${IIS_RESOURCE_MC}"
    DEPENDS "${IIS_RESOURCE_MC}"
    COMMENT "Generating resource files from ${IIS_RESOURCE_MC}"
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

# Source files for IIS-specific components
set(IIS_MODULE_SOURCES
    main.cpp
    moduleconfig.cpp
    mymodule.cpp
    mymodule.def
    ${MC_GENERATED_RC}
)

set_source_files_properties(
    ${MC_GENERATED_RC}
    ${MC_GENERATED_H}
    PROPERTIES GENERATED TRUE
)

add_library(${IIS_MODULE_NAME} SHARED 
    ${IIS_APACHE_SOURCES}
    ${IIS_STANDALONE_SOURCES}
    ${IIS_MODULE_SOURCES}
)

# Set the output name and extension
set_target_properties(${IIS_MODULE_NAME} PROPERTIES
    OUTPUT_NAME ${IIS_MODULE_NAME}
    PREFIX ""
    SUFFIX ".dll"
)

# Include directories
target_include_directories(${IIS_MODULE_NAME} PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/..
    ${CMAKE_CURRENT_SOURCE_DIR}/../apache2
    ${CMAKE_CURRENT_SOURCE_DIR}/../apache2/libinjection
    ${LIBXML2_INCLUDE_DIR}/libxml
    ${PCRE2_INCLUDE_DIRS}
    ${CURL_INCLUDE_DIRS}
    ${APR_INCLUDE_DIRS}
    ${CMAKE_CURRENT_BINARY_DIR}
)

# Include standalone directory if it exists
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../standalone)
    target_include_directories(${IIS_MODULE_NAME} PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/../standalone
    )
endif()

# Apache-specific includes
if(APACHE_ROOT)
    if(NOT EXISTS "${APACHE_ROOT}")
        message(FATAL_ERROR "APACHE_ROOT is defined but the directory '${APACHE_ROOT}' does not exist. Please set APACHE_ROOT to a valid Apache installation directory.")
    endif()
    if(NOT EXISTS "${APACHE_ROOT}/lib")
        message(FATAL_ERROR "APACHE_ROOT/lib directory does not exist. Expected: '${APACHE_ROOT}/lib'. Please ensure Apache libraries are available.")
    endif()

    file(TO_CMAKE_PATH "${APACHE_ROOT}" APACHE_ROOT)

    # Create imported targets for Apache libraries
    add_library(Apache::httpd SHARED IMPORTED)
    set_target_properties(Apache::httpd PROPERTIES
        INTERFACE_INCLUDE_DIRECTORIES "${APACHE_ROOT}/include"
        IMPORTED_IMPLIB "${APACHE_ROOT}/lib/libhttpd.lib"
        IMPORTED_LOCATION "${APACHE_ROOT}/bin/libhttpd.dll"
    )

    add_library(Apache::apr SHARED IMPORTED)
    set_target_properties(Apache::apr PROPERTIES
        IMPORTED_IMPLIB "${APACHE_ROOT}/lib/libapr-1.lib"
        IMPORTED_LOCATION "${APACHE_ROOT}/bin/libapr-1.dll"
    )

    add_library(Apache::aprutil SHARED IMPORTED)
    set_target_properties(Apache::aprutil PROPERTIES
        IMPORTED_IMPLIB "${APACHE_ROOT}/lib/libaprutil-1.lib"
        IMPORTED_LOCATION "${APACHE_ROOT}/bin/libaprutil-1.dll"
    )

    add_library(Apache::apriconv SHARED IMPORTED)
    set_target_properties(Apache::apriconv PROPERTIES
        IMPORTED_IMPLIB "${APACHE_ROOT}/lib/libapriconv-1.lib"
        IMPORTED_LOCATION "${APACHE_ROOT}/bin/libapriconv-1.dll"
    )

    target_include_directories(${IIS_MODULE_NAME} PRIVATE
        ${APACHE_ROOT}/include
    )
endif()

# Compile definitions to match the original Makefile.win
set(MODSECURITY_VERSION_FLAG "VERSION_IIS") # Define the version flag string
target_compile_definitions(${IIS_MODULE_NAME} PRIVATE
    WIN32
    WINNT
    inline=APR_INLINE
    AP_DECLARE_STATIC
    WITH_CURL
    WITH_REMOTE_RULES
    MSC_LARGE_STREAM_INPUT
    WITH_YAJL
    ${MODSECURITY_VERSION_FLAG}  # Use the defined version flag
)

option(WITH_LUA "Enable Lua support" OFF)
# Optional compile definitions
if(WITH_LUA)
    find_package(Lua CONFIG REQUIRED)
    target_compile_definitions(${IIS_MODULE_NAME} PRIVATE WITH_LUA)
    target_include_directories(${IIS_MODULE_NAME} PRIVATE ${LUA_INCLUDE_DIR})
endif()

option(WITH_YAJL "Enable YAJL support" OFF)
if(WITH_YAJL)
    # Manually find YAJL if config.cmake is not available (e.g., from vcpkg)
    find_path(YAJL_INCLUDE_DIR yajl/yajl_common.h
        PATHS "${CMAKE_CURRENT_SOURCE_DIR}/build-${ARCHITECTURE}/vcpkg_installed/${ARCHITECTURE}-windows/include"
        NO_DEFAULT_PATH
    )
    find_library(YAJL_LIBRARY NAMES yajl
        PATHS "${CMAKE_CURRENT_SOURCE_DIR}/build-${ARCHITECTURE}/vcpkg_installed/${ARCHITECTURE}-windows/lib"
        NO_DEFAULT_PATH
    )

    if(YAJL_INCLUDE_DIR AND YAJL_LIBRARY)
        set(YAJL_INCLUDE_DIRS ${YAJL_INCLUDE_DIR})
        set(YAJL_LIBRARIES ${YAJL_LIBRARY})
        target_compile_definitions(${IIS_MODULE_NAME} PRIVATE WITH_YAJL)
        target_include_directories(${IIS_MODULE_NAME} PRIVATE ${YAJL_INCLUDE_DIRS})
    else()
        message(WARNING "YAJL not found. YAJL_INCLUDE_DIR: '${YAJL_INCLUDE_DIR}', YAJL_LIBRARY: '${YAJL_LIBRARY}'. Please ensure yajl is installed via vcpkg in the vcpkg_installed directory. Disabling YAJL support.")
        option(WITH_YAJL "Enable YAJL support" OFF) # Disable if not found
    endif() 
endif()

option(WITH_SSDEEP "Enable SSDEEP support" OFF)
if(WITH_SSDEEP)

    set(SSDEEP_ROOT "" CACHE PATH "Path to manually built ssdeep")
    if(NOT SSDEEP_ROOT OR NOT EXISTS "${SSDEEP_ROOT}")
        message(WARNING "SSDEEP_ROOT is not defined or path does not exist. Current SSDEEP_ROOT: '${SSDEEP_ROOT}'. Please set SSDEEP_ROOT to the ssdeep installation directory. Disabling SSDEEP support.")
        set(WITH_SSDEEP OFF CACHE BOOL "Enable SSDEEP support" FORCE)
    else()

        file(TO_CMAKE_PATH "${SSDEEP_ROOT}" SSDEEP_ROOT)

        message(STATUS "SSDEEP_ROOT: ${SSDEEP_ROOT}")

        find_path(SSDEEP_INCLUDE_DIR fuzzy.h
            PATHS "${SSDEEP_ROOT}/include"
            NO_DEFAULT_PATH
        )
        
        if(SSDEEP_INCLUDE_DIR)
            message(STATUS "Found manually built ssdeep include: ${SSDEEP_INCLUDE_DIR}")
            target_compile_definitions(${IIS_MODULE_NAME} PRIVATE WITH_SSDEEP)
            target_include_directories(${IIS_MODULE_NAME} PRIVATE ${SSDEEP_INCLUDE_DIR})

            set(SSDEEP_DEF_FILE "${SSDEEP_ROOT}/fuzzy.def")
            if(NOT EXISTS "${SSDEEP_DEF_FILE}")
                message(WARNING "fuzzy.def not found at ${SSDEEP_DEF_FILE}. Disabling SSDEEP support.")
                set(WITH_SSDEEP OFF CACHE BOOL "Enable SSDEEP support" FORCE)
            else()
                set(SSDEEP_GENERATED_LIB "${CMAKE_CURRENT_BINARY_DIR}/fuzzy.lib")
                set(SSDEEP_GENERATED_dll "${CMAKE_CURRENT_BINARY_DIR}/bin/fuzzy.dll")
                
                add_custom_command(
                    OUTPUT ${SSDEEP_GENERATED_LIB}
                    COMMAND lib.exe /machine:${ARCHITECTURE} /def:${SSDEEP_DEF_FILE} /out:${SSDEEP_GENERATED_LIB}
                    DEPENDS "${SSDEEP_DEF_FILE}"
                    COMMENT "Generating SSDEEP .lib from .def for MSVC"
                    VERBATIM
                )
                
                set_source_files_properties(${SSDEEP_GENERATED_LIB} PROPERTIES GENERATED TRUE)
                
                add_custom_target(generate_ssdeep_lib ALL
                    DEPENDS ${SSDEEP_GENERATED_LIB}
                    COMMENT "Ensuring ssdeep lib is generated"
                )
                
                add_dependencies(${IIS_MODULE_NAME} generate_ssdeep_lib)
                
                add_library(SSDEEP::fuzzy SHARED IMPORTED)
                set_target_properties(SSDEEP::fuzzy PROPERTIES
                    INTERFACE_INCLUDE_DIRECTORIES "${SSDEEP_INCLUDE_DIR}"
                    IMPORTED_LOCATION "${SSDEEP_GENERATED_dll}"
                    IMPORTED_IMPLIB "${SSDEEP_GENERATED_LIB}"
                )
                 
             endif()
        endif()
    endif()
endif()

# Compiler-specific options for MSVC to match the original Makefile.win
if(MSVC)
    target_compile_options(${IIS_MODULE_NAME} PRIVATE
        /nologo
        /O2
        /W3
        /wd4244
        /wd4018
        /MD
        /Zi
    )
    
    # Linker options to match the original Makefile.win
    set_target_properties(${IIS_MODULE_NAME} PROPERTIES
        LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"
    )
endif()

# Link libraries to match the original Makefile.win
target_link_libraries(${IIS_MODULE_NAME} PRIVATE
    LibXml2::LibXml2
    PCRE2::8BIT
    CURL::libcurl
    kernel32
    user32
    gdi32
    winspool
    comdlg32
    advapi32
    shell32
    ole32
    oleaut32
    uuid
    odbc32
    odbccp32
    ws2_32
    iphlpapi
)

# Apache-specific libraries
if(APACHE_ROOT)
    target_link_libraries(${IIS_MODULE_NAME} PRIVATE
        Apache::httpd
        Apache::apr
        Apache::aprutil
        Apache::apriconv
    )
else()
    message(WARNING "APACHE_ROOT is not defined or path does not exist. Current APACHE_ROOT: '${APACHE_ROOT}'. Please set APACHE_ROOT to the Apache installation directory.")
endif()

# Optional link libraries
if(WITH_LUA)
    target_link_libraries(${IIS_MODULE_NAME} PRIVATE ${LUA_LIBRARIES})
endif()

if(WITH_YAJL)
    target_link_libraries(${IIS_MODULE_NAME} PRIVATE ${YAJL_LIBRARIES})
endif()

if(WITH_SSDEEP AND SSDEEP_INCLUDE_DIR AND SSDEEP_GENERATED_LIB)
    target_link_libraries(${IIS_MODULE_NAME} PRIVATE SSDEEP::fuzzy)
endif()

if(APACHE_ROOT AND EXISTS "${APACHE_ROOT}/bin")
    add_custom_command(TARGET ${IIS_MODULE_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${APACHE_ROOT}/bin/libhttpd.dll"
            $<TARGET_FILE_DIR:${IIS_MODULE_NAME}>
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${APACHE_ROOT}/bin/libaprutil-1.dll"
            $<TARGET_FILE_DIR:${IIS_MODULE_NAME}>
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${APACHE_ROOT}/bin/libapriconv-1.dll"
            $<TARGET_FILE_DIR:${IIS_MODULE_NAME}>
        COMMENT "Copying Apache DLLs to output directory"
    )
else()
    message(WARNING "APACHE_ROOT is not defined or path does not exist. Current APACHE_ROOT: '${APACHE_ROOT}'. Please set APACHE_ROOT to the Apache installation directory.")
endif()

if(WITH_SSDEEP AND SSDEEP_ROOT AND EXISTS "${SSDEEP_ROOT}/bin/fuzzy.dll")
    add_custom_command(TARGET ${IIS_MODULE_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${SSDEEP_ROOT}/bin/fuzzy.dll"
            $<TARGET_FILE_DIR:${IIS_MODULE_NAME}>
        COMMENT "Copying SSDEEP DLL to output directory"
    )
endif()


# Install target - copy to release files directory
install(TARGETS ${IIS_MODULE_NAME}
    RUNTIME DESTINATION .
    LIBRARY DESTINATION .
)

if(APACHE_ROOT AND EXISTS "${APACHE_ROOT}/bin")
    install(FILES
        "${APACHE_ROOT}/bin/libhttpd.dll"
        "${APACHE_ROOT}/bin/libaprutil-1.dll"
        "${APACHE_ROOT}/bin/libapriconv-1.dll"
        DESTINATION .
    )
endif()

if(WITH_SSDEEP AND SSDEEP_ROOT AND EXISTS "${SSDEEP_ROOT}/bin/fuzzy.dll")
    install(FILES
        "${SSDEEP_ROOT}/bin/fuzzy.dll"
        DESTINATION .
    )
endif()

# Also install the PDB file if it's generated
install(FILES $<TARGET_PDB_FILE:${IIS_MODULE_NAME}> DESTINATION . OPTIONAL)

2. Windows CI/CD

name: CI/CD for IIS Module

on:
  push:
    branches:
      - v2/test-ci-windows
  pull_request:
    branches:
      - v2/test-ci-windows

jobs:
  build:
    strategy:
      matrix:
        arch: [x86, x64]
    runs-on: windows-latest
    
    # For Caching
    permissions:
      actions: read
      contents: read

    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Install Apache for x86
      if: matrix.arch == 'x86'
      shell: pwsh
      run: |
        $apachePath = "${{ github.workspace }}\apache-x86"
        New-Item -ItemType Directory -Path $apachePath -Force
        choco install apache-httpd -y --force --forcex86 --no-progress -r --params="'/installLocation:$apachePath /noService'"
        echo "APACHE_ROOT=$apachePath\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

    - name: Set Apache path for x64
      if: matrix.arch == 'x64'
      shell: pwsh
      run: |
        echo "APACHE_ROOT=C:\tools\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

    # - name: Setup MSYS2
    #   uses: msys2/setup-msys2@v2
    #   with:
    #     msystem: ${{ matrix.arch == 'x86' && 'MINGW32' || 'UCRT64' }}
    #     update: true
    #     install: >
    #       git
    #       make
    #       autoconf
    #       automake
    #       libtool
    #       ${{ matrix.arch == 'x86' && 'mingw-w64-i686-gcc' || 'mingw-w64-ucrt-x86_64-gcc' }}
    #       ${{ matrix.arch == 'x86' && 'mingw-w64-i686-pkg-config' || 'mingw-w64-ucrt-x86_64-pkg-config' }}

    # - name: Clone and build ssdeep
    #   shell: msys2 {0}
    #   run: |
    #     MSYS2_WORKSPACE=$(cygpath -u '${{ github.workspace }}')
    #     echo "Converted workspace path: $MSYS2_WORKSPACE"

    #     git clone https://github.com/ssdeep-project/ssdeep.git --depth 1
    #     cd ssdeep
    #     autoreconf -i
        
    #     if [ "${{ matrix.arch }}" = "x86" ]; then
    #       ./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3" --build=i686-pc-mingw32
    #     else
    #       ./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3"
    #     fi
        
    #     make dll

    #     mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/bin"
    #     mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/include"
    #     cp -v fuzzy.dll "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/bin/"
    #     cp -v fuzzy.h "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/include/"
    #     cp -v fuzzy.def "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/"

    - name: Restore vcpkg cache
      id: vcpkg-cache
      uses: TAServers/vcpkg-cache@v3
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        prefix: vcpkg-iis-module-${{ matrix.arch }}/

    - name: Configure CMake for IIS Module
      env:
        VCPKG_FEATURE_FLAGS: "binarycaching"
        VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite"
        VCPKG_DEFAULT_TRIPLET: ${{ matrix.arch }}-windows
      run: |
        $archFlag = "${{ matrix.arch }}"
        $cmakeArch = if ($archFlag -eq "x86") { "Win32" } else { "x64" }
        $installDir = if ($archFlag -eq "x86") { "x86" } else { "amd64" }

        cmake `
        -DAPACHE_ROOT="$env:APACHE_ROOT" `
        -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\iis\release\$installDir" `
        -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" `
        -A $cmakeArch `
        -DWITH_LUA=ON `
        -DWITH_YAJL=ON `
        -S IIS -B "iis\build-${{ matrix.arch }}"

      # -DSSDEEP_ROOT="${{ github.workspace }}\ssdeep-install-${{ matrix.arch }}" `
      # -DWITH_SSDEEP=ON `

    - name: Build IIS Module
      shell: pwsh
      run: |
        cmake --build "iis\build-${{ matrix.arch }}" --config Release

    - name: Upload artifacts
      uses: actions/upload-artifact@v4
      with:
        name: iis-module-${{ matrix.arch }}
        path: iis/build-${{ matrix.arch }}/Release/
    
  package:
    needs: build
    runs-on: windows-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Download x64 artifacts
      uses: actions/download-artifact@v4
      with:
        name: iis-module-x64
        path: iis/release/amd64/

    - name: Download x86 artifacts
      uses: actions/download-artifact@v4
      with:
        name: iis-module-x86
        path: iis/release/x86/

    - name: Generate MSI files
      shell: pwsh
      run: |
        heat dir "iis\release\amd64" -cg ModSec64Components -dr inetsrv64 -gg -sreg -srd -var var.ModSecurityIISRelease64 -out "iis\ModSec64.wxs"
        heat dir "iis\release\x86" -cg ModSec32Components -dr inetsrv32 -gg -sreg -srd -var var.ModSecurityIISRelease32 -out "iis\ModSec32.wxs"
        candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wxs" "iis\ModSec64.wxs" -arch x64 -dModSecurityIISRelease64="iis\release\amd64\" -out iis\
        candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\ModSec32.wxs" -arch x86 -dModSecurityIISRelease32="iis\release\x86\" -out iis\
        light.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wixobj" "iis\ModSec32.wixobj" "iis\ModSec64.wixobj" -out "iis\modsecurityiis.msi"

    - name: Upload artifacts
      uses: actions/upload-artifact@v4
      with:
        name: modsecurityiis-installers
        path: iis/modsecurityiis.msi

  test:
    needs: package
    runs-on: windows-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Download MSI files
      uses: actions/download-artifact@v4
      with:
        name: modsecurityiis-installers
        path: ${{ github.workspace }}\

    - name: Install MSI
      shell: pwsh
      run: |
        $msiPath = "${{ github.workspace }}\modsecurityiis.msi"
        if (-not (Test-Path $msiPath)) {
          Write-Error "MSI file not found at $msiPath"
          exit 1
        }
        
        # Install with logging for debugging
        $installLog = "${{ github.workspace }}\install.log"
        $installResult = Start-Process -FilePath "msiexec.exe" -ArgumentList @(
          "/i", "`"$msiPath`"",
          "/qn",
          "/norestart",
          "/l*", "`"$installLog`""
        ) -Wait -PassThru
        
        if ($installResult.ExitCode -ne 0) {
          Write-Error "MSI installation failed with exit code $($installResult.ExitCode)"
          Get-Content $installLog | Write-Host
          exit 1
        }
        
        $installDir = "C:\Program Files\ModSecurity IIS"
        $requiredFiles = @(
          "modsecurity.conf",
          "modsecurity_iis.conf"
        )
        
        foreach ($file in $requiredFiles) {
          $filePath = Join-Path $installDir $file
          if (-not (Test-Path $filePath)) {
            Write-Error "Required file $file not found in installation directory"
            exit 1
          }
        }

    - name: Install OWASP Core Rules
      shell: pwsh
      run: |
        $crsVersion = "v4.18.0"
        $crsUrl = "https://github.com/coreruleset/coreruleset/archive/refs/tags/$crsVersion.tar.gz"
        $crsDir = "C:\Program Files\ModSecurity IIS\coreruleset"
        $modSecurityConfigDir = "C:\Program Files\ModSecurity IIS"

        try {
          New-Item -ItemType Directory -Path $crsDir -Force
          Invoke-WebRequest -Uri $crsUrl -OutFile "$crsDir\$crsVersion.tar.gz"
          tar -xzf "$crsDir\$crsVersion.tar.gz" -C $crsDir --strip-components=1

          Get-ChildItem "$crsDir" -Recurse -Filter "*.example" | ForEach-Object {
              $newName = $_.Name.Replace(".example", "")
              Rename-Item -Path $_.FullName -NewName $newName
          }
          
          $modSecurityConfigFile = "$modSecurityConfigDir\modsecurity_iis.conf"

          $crsRules = @(
            "Include coreruleset/crs-setup.conf",
            "Include coreruleset/rules/*.conf",
            "Include coreruleset/plugins/*-config.conf",
            "Include coreruleset/plugins/*-before.conf",
            "Include coreruleset/rules/*.conf",
            "Include coreruleset/plugins/*-after.conf"
          )

          Add-Content -Path $modSecurityConfigFile -Value $crsRules

          (Get-Content -Path $modSecurityConfigDir\modsecurity.conf) -replace 'SecRuleEngine DetectionOnly', 'SecRuleEngine On' | Set-Content -Path $modSecurityConfigDir\modsecurity.conf

        }
        catch {
            Write-Error "Failed to install OWASP Core Rules: $($_.Exception.Message)"
            exit 1
        }

    - name: Test IIS Module
      shell: pwsh
      run: |
        $iisConfigDir = "C:\Program Files\ModSecurity IIS\"

        Restart-Service W3SVC -Force

        $modules = & "$env:SystemRoot\system32\inetsrv\appcmd.exe" list modules
        if ($LASTEXITCODE -ne 0) {
            Write-Error "appcmd failed with exit code $LASTEXITCODE"
            exit 1
        }

        if (-not ($modules -match "ModSecurity")) {
            Write-Error "ModSecurity module not found in IIS modules"
            Write-Host "IIS modules: $modules"
            exit 1
        }

        $testCases = @(
            @{Url = "http://localhost/"; Description = "Normal request"; ExpectedCode = 200},
            @{Url = "http://localhost/?id=1' OR '1'='1"; Description = "SQL injection attempt"; ExpectedCode = 403},
            @{Url = "http://localhost/?q=<script>alert('test')</script>"; Description = "XSS attempt"; ExpectedCode = 403}
        )
        
        foreach ($test in $testCases) {
            try {
                $response = Invoke-WebRequest $test.Url -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30
                
                if ($response.StatusCode -eq $test.ExpectedCode) {
                    Write-Host "PASS: $($test.Description) - returned $($response.StatusCode)"
                }
                else {
                    Write-Host "FAIL: $($test.Description) - expected $($test.ExpectedCode) but got $($response.StatusCode)"
                }
            }
            catch {
                Write-Host "ERROR: $($test.Description) - request failed: $($_.Exception.Message)"
            }
        }
        

        # Check event log
        $badMessagePattern = 'Failed to find the RegisterModule entrypoint|The description for Event ID|The data is the error|dll failed to load'

        $events = Get-EventLog -LogName Application -Newest 100 |
            Where-Object { $_.Message -match $badMessagePattern } |
            Where-Object { $_.Source -match 'IIS|W3SVC|mscor|IIS-W3SVC|IIS-W3WP|ModSecurity' }

        if ($events -and $events.Count -gt 0) {
            Write-Host '::error:: Found errors in event log'
            $events | Select-Object TimeGenerated, Source, EntryType, EventID, Message | Format-List
            Exit 1
        }

        Get-EventLog -LogName Application -Source ModSecurity -Newest 10

See:
https://github.com/A13501350/ModSecurity/actions/runs/17894429285

Hi @A13501350,

I implemented a CMake-based build system and GitHub Actions CI/CD pipeline for the ModSecurity IIS module on Windows.

This is AWESOME!

Many thanks for this, I try to add it to our CI soon!

Thanks again!

​​Glad you found it useful. This implementation is more of a proof-of-concept, especially the CMake scripts. There are a few additional elements needed for full functionality, such as a proper vcpkg.json manifest file and some modifications to the installer.wxs WiX configuration.​​

​​Glad you found it useful. This implementation is more of a proof-of-concept, especially the CMake scripts. There are a few additional elements needed for full functionality, such as a proper vcpkg.json manifest file and some modifications to the installer.wxs WiX configuration.​​

Thanks. I reviewed that branch and I think that would be very useful. As I see this file does the tasks, right? And you add not just the IIS module build and test but Apache module too, right?

Please send that as a PR (after you remove the commented (unnecessary) lines). Thanks!

I'm not sure what you mean by the Apache module. I only added the build and test for the IIS module, along with some related modifications, of course.

I'm not sure what you mean by the Apache module. I only added the build and test for the IIS module, along with some related modifications, of course.

Sorry, may be this line confused me, because here you (probably) install Apache2. But may be it needs only because it's necessary to build IIS module?

I am not following the above with reagrds to a fix, but I am getting "Incorrect function" all the time in events for modsecurity.
how can I fix this please

I'm not sure what you mean by the Apache module. I only added the build and test for the IIS module, along with some related modifications, of course.

Sorry, may be this line confused me, because here you (probably) install Apache2. But may be it needs only because it's necessary to build IIS module?

Yes. The IIS module for ModSecurity v2 essentially wraps the core ModSecurity components, which requires the Apache libraries.