Browse Source

Fix ANCM environment variables bugs (#6083)

Pavel Krymets 7 years ago
parent
commit
a7b783724e
20 changed files with 239 additions and 585 deletions
  1. 1 1
      .azure/pipelines/ci.yml
  2. 12 0
      src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp
  3. 3 0
      src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h
  4. 7 2
      src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp
  5. 9 0
      src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h
  6. 11 0
      src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/exceptions.h
  7. 1 1
      src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp
  8. 3 2
      src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h
  9. 9 26
      src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp
  10. 23 19
      src/Servers/IIS/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp
  11. 3 2
      src/Servers/IIS/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h
  12. 4 5
      src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h
  13. 30 373
      src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h
  14. 9 103
      src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp
  15. 2 3
      src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h
  16. 101 19
      src/Servers/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs
  17. 6 0
      src/Servers/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs
  18. 0 23
      src/Servers/IIS/test/testassets/InProcessWebSite/Startup.cs
  19. 0 6
      src/Servers/IIS/test/testassets/InProcessWebSite/web.config
  20. 5 0
      src/Servers/IIS/test/testassets/shared/SharedStartup/Startup.shared.cs

+ 1 - 1
.azure/pipelines/ci.yml

@@ -37,7 +37,7 @@ jobs:
 - template: jobs/iisintegration-job.yml
   parameters:
     TestGroupName: IISExpress
-    skipArgs: "/p:SkipIISBackwardsCompatibilityTests=false /p:SkipIISTests=true /p:SkipIISExpressTests=false /p:SkipIISForwardsCompatibilityTests=true"
+    skipArgs: "/p:SkipIISBackwardsCompatibilityTests=true /p:SkipIISTests=true /p:SkipIISExpressTests=false /p:SkipIISForwardsCompatibilityTests=true"
 - template: jobs/iisintegration-job.yml
   parameters:
     TestGroupName: IISForwardCompat

+ 12 - 0
src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp

@@ -5,6 +5,7 @@
 
 #include "StringHelpers.h"
 #include "ConfigurationLoadException.h"
+#include <map>
 
 std::wstring ConfigurationSection::GetRequiredString(const std::wstring& name)  const
 {
@@ -63,6 +64,17 @@ std::vector<std::pair<std::wstring, std::wstring>> ConfigurationSection::GetKeyV
     return pairs;
 }
 
+std::map<std::wstring, std::wstring, ignore_case_comparer> ConfigurationSection::GetMap(const std::wstring& name) const
+{
+    std::map<std::wstring, std::wstring, ignore_case_comparer> pairs;
+
+    for (auto const element : GetRequiredSection(name)->GetCollection())
+    {
+        pairs.insert_or_assign(element->GetRequiredString(CS_ASPNETCORE_COLLECTION_ITEM_NAME), element->GetString(CS_ASPNETCORE_COLLECTION_ITEM_VALUE).value_or(L""));
+    }
+    return pairs;
+}
+
 std::shared_ptr<ConfigurationSection> ConfigurationSection::GetRequiredSection(const std::wstring& name) const
 {
     auto section = GetSection(name);

+ 3 - 0
src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h

@@ -6,8 +6,10 @@
 #include <string>
 #include <optional>
 #include <vector>
+#include <map>
 
 #include "NonCopyable.h"
+#include "StringHelpers.h"
 
 #define CS_ASPNETCORE_COLLECTION_ITEM_NAME               L"name"
 #define CS_ASPNETCORE_COLLECTION_ITEM_VALUE              L"value"
@@ -46,6 +48,7 @@ public:
     DWORD GetRequiredTimespan(const std::wstring& name)  const;
 
     virtual std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const;
+    virtual std::map<std::wstring, std::wstring, ignore_case_comparer> GetMap(const std::wstring& name) const;
 
     virtual std::shared_ptr<ConfigurationSection> GetRequiredSection(const std::wstring & name) const;
 

+ 7 - 2
src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp

@@ -17,13 +17,18 @@ bool ends_with(const std::wstring &source, const std::wstring &suffix, bool igno
 
 bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2)
 {
-    return CSTR_EQUAL == CompareStringOrdinal(s1.c_str(), static_cast<int>(s1.length()), s2.c_str(), static_cast<int>(s2.length()), true);
+    return compare_ignore_case(s1, s2) == 0;
+}
+
+int compare_ignore_case(const std::wstring& s1, const std::wstring& s2)
+{
+    return CompareStringOrdinal(s1.c_str(), static_cast<int>(s1.length()), s2.c_str(), static_cast<int>(s2.length()), true) - CSTR_EQUAL;
 }
 
 std::wstring to_wide_string(const std::string &source, const unsigned int codePage)
 {
     // MultiByteToWideChar returns 0 on failure, which is also the same return value
-    // for empty strings. Preemptive return. 
+    // for empty strings. Preemptive return.
     if (source.length() == 0)
     {
         return L"";

+ 9 - 0
src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h

@@ -11,6 +11,9 @@ bool ends_with(const std::wstring &source, const std::wstring &suffix, bool igno
 [[nodiscard]]
 bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2);
 
+[[nodiscard]]
+int compare_ignore_case(const std::wstring& s1, const std::wstring& s2);
+
 [[nodiscard]]
 std::wstring to_wide_string(const std::string &source, const unsigned int codePage);
 
@@ -48,3 +51,9 @@ std::string format(const std::string& format, Args ... args)
     return result;
 }
 
+struct ignore_case_comparer
+{
+    bool operator() (const std::wstring & s1, const std::wstring & s2) const {
+        return compare_ignore_case(s1, s2) == -1;
+    }
+};

+ 11 - 0
src/Servers/IIS/src/AspNetCoreModuleV2/CommonLib/exceptions.h

@@ -134,6 +134,12 @@ private:
     return condition;
 }
 
+ __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS const InvalidOperationException& exception)
+{
+    TraceException(LOCATION_CALL exception);
+    DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "InvalidOperationException '%ls' caught at " LOCATION_FORMAT, exception.as_wstring().c_str(), LOCATION_CALL_ONLY);
+}
+
  __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS const std::exception& exception)
 {
     TraceException(LOCATION_CALL exception);
@@ -165,6 +171,11 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON
         ReportException(LOCATION_CALL exception);
         return exception.GetResult();
     }
+    catch (const InvalidOperationException& exception)
+    {
+        ReportException(LOCATION_CALL exception);
+        return HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION);
+    }
     catch (const std::exception& exception)
     {
         ReportException(LOCATION_CALL exception);

+ 1 - 1
src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp

@@ -53,7 +53,7 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
     m_fStdoutLogEnabled = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED);
     m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE);
     m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE);
-    m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES);
+    m_environmentVariables = aspNetCoreSection->GetMap(CS_ASPNETCORE_ENVIRONMENT_VARIABLES);
 
     const auto handlerSettings = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);
     m_fSetCurrentDirectory = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY).value_or(L"true"), L"true");

+ 3 - 2
src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h

@@ -7,6 +7,7 @@
 #include "BindingInformation.h"
 #include "ConfigurationSource.h"
 #include "WebConfigConfigurationSource.h"
+#include <map>
 
 class InProcessOptions: NonCopyable
 {
@@ -87,7 +88,7 @@ public:
         return m_dwShutdownTimeLimitInMS;
     }
 
-    const std::vector<std::pair<std::wstring, std::wstring>>&
+    const std::map<std::wstring, std::wstring, ignore_case_comparer>&
     QueryEnvironmentVariables() const
     {
         return m_environmentVariables;
@@ -120,7 +121,7 @@ private:
     bool                           m_fAnonymousAuthEnabled;
     DWORD                          m_dwStartupTimeLimitInMS;
     DWORD                          m_dwShutdownTimeLimitInMS;
-    std::vector<std::pair<std::wstring, std::wstring>> m_environmentVariables;
+    std::map<std::wstring, std::wstring, ignore_case_comparer> m_environmentVariables;
     std::vector<BindingInformation> m_bindingInformation;
 
 protected:

+ 9 - 26
src/Servers/IIS/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp

@@ -425,37 +425,20 @@ IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrCont
 HRESULT
 IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
 {
-    auto variables = m_pConfig->QueryEnvironmentVariables();
-
-    auto inputTable = std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER>(new ENVIRONMENT_VAR_HASH());
-    RETURN_IF_FAILED(inputTable->Initialize(37 /*prime*/));
-    // Copy environment variables to old style hash table
-    for (auto & variable : variables)
-    {
-        auto pNewEntry = std::unique_ptr<ENVIRONMENT_VAR_ENTRY, ENVIRONMENT_VAR_ENTRY_DELETER>(new ENVIRONMENT_VAR_ENTRY());
-        RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str()));
-        RETURN_IF_FAILED(inputTable->InsertRecord(pNewEntry.get()));
-    }
-
-    ENVIRONMENT_VAR_HASH* pHashTable = NULL;
-    std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER> table;
-    RETURN_IF_FAILED(ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
-        inputTable.get(),
+    auto variables = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
+        m_pConfig->QueryEnvironmentVariables(),
         m_pConfig->QueryWindowsAuthEnabled(),
         m_pConfig->QueryBasicAuthEnabled(),
         m_pConfig->QueryAnonymousAuthEnabled(),
+        false, // fAddHostingStartup
         QueryApplicationPhysicalPath().c_str(),
-        nullptr, /* pHttpsPort */
-        &pHashTable));
+        nullptr);
 
-    table.reset(pHashTable);
-
-    HRESULT hr = S_OK;
-    table->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr);
-    RETURN_IF_FAILED(hr);
-
-    table->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr);
-    RETURN_IF_FAILED(hr);
+    for (const auto & variable : variables)
+    {
+        LOG_INFOF(L"Setting environment variable %ls=%ls", variable.first.c_str(), variable.second.c_str());
+        SetEnvironmentVariable(variable.first.c_str(), variable.second.c_str());
+    }
 
     return S_OK;
 }

+ 23 - 19
src/Servers/IIS/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp

@@ -24,7 +24,7 @@ SERVER_PROCESS::Initialize(
     BOOL                  fWindowsAuthEnabled,
     BOOL                  fBasicAuthEnabled,
     BOOL                  fAnonymousAuthEnabled,
-    ENVIRONMENT_VAR_HASH *pEnvironmentVariables,
+    std::map<std::wstring, std::wstring, ignore_case_comparer>& pEnvironmentVariables,
     BOOL                  fStdoutLogEnabled,
     BOOL                  fWebSocketSupported,
     STRU                  *pstruStdoutLogFile,
@@ -761,6 +761,8 @@ SERVER_PROCESS::StartProcess(
     ENVIRONMENT_VAR_HASH    *pHashTable = NULL;
     PWSTR                   pStrStage = NULL;
     BOOL                    fCriticalError = FALSE;
+    std::map<std::wstring, std::wstring, ignore_case_comparer> variables;
+
     GetStartupInfoW(&startupInfo);
 
     //
@@ -782,27 +784,30 @@ SERVER_PROCESS::StartProcess(
             goto Failure;
         }
 
-        if (FAILED_LOG(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
-            m_pEnvironmentVarTable,
-            m_fWindowsAuthEnabled,
-            m_fBasicAuthEnabled,
-            m_fAnonymousAuthEnabled,
-            m_struAppFullPath.QueryStr(),
-            m_struHttpsPort.QueryStr(),
-            &pHashTable)))
+        try
         {
-            pStrStage = L"InitEnvironmentVariablesTable";
-            goto Failure;
+            variables = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
+                m_pEnvironmentVarTable,
+                m_fWindowsAuthEnabled,
+                m_fBasicAuthEnabled,
+                m_fAnonymousAuthEnabled,
+                true, // fAddHostingStartup
+                m_struAppFullPath.QueryStr(),
+                m_struHttpsPort.QueryStr());
+
+            variables = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables(variables, m_fWebSocketSupported);
         }
+        CATCH_RETURN();
 
-        if (FAILED_LOG(hr = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables(
-            pHashTable,
-            m_fWebSocketSupported
-        )))
-        {
-            pStrStage = L"AddWebsocketEnabledToEnvironmentVariables";
-            goto Failure;
 
+        pHashTable = new ENVIRONMENT_VAR_HASH();
+        RETURN_IF_FAILED(pHashTable->Initialize(37 /*prime*/));
+        // Copy environment variables to old style hash table
+        for (auto & variable : variables)
+        {
+            auto pNewEntry = std::unique_ptr<ENVIRONMENT_VAR_ENTRY, ENVIRONMENT_VAR_ENTRY_DELETER>(new ENVIRONMENT_VAR_ENTRY());
+            RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str()));
+            RETURN_IF_FAILED(pHashTable->InsertRecord(pNewEntry.get()));
         }
 
         //
@@ -1793,7 +1798,6 @@ SERVER_PROCESS::~SERVER_PROCESS()
 
     CleanUp();
 
-    m_pEnvironmentVarTable = NULL;
     // no need to free m_pEnvironmentVarTable, as it references to
     // the same hash table hold by configuration.
     // the hashtable memory will be freed once onfiguration got recycled

+ 3 - 2
src/Servers/IIS/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h

@@ -4,6 +4,7 @@
 #pragma once
 
 #include <random>
+#include <map>
 
 #define MIN_PORT                                    1025
 #define MAX_PORT                                    48000
@@ -33,7 +34,7 @@ public:
         _In_ BOOL                  fWindowsAuthEnabled,
         _In_ BOOL                  fBasicAuthEnabled,
         _In_ BOOL                  fAnonymousAuthEnabled,
-        _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables,
+        _In_ std::map<std::wstring, std::wstring, ignore_case_comparer>& pEnvironmentVariables,
         _In_ BOOL                  fStdoutLogEnabled,
         _In_ BOOL                  fWebSocketSupported,
         _In_ STRU                 *pstruStdoutLogFile,
@@ -290,5 +291,5 @@ private:
     HANDLE                  m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES];
 
     PROCESS_MANAGER         *m_pProcessManager;
-    ENVIRONMENT_VAR_HASH    *m_pEnvironmentVarTable ;
+    std::map<std::wstring, std::wstring, ignore_case_comparer> m_pEnvironmentVarTable;
 };

+ 4 - 5
src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h

@@ -4,12 +4,11 @@
 #pragma once
 
 #define HOSTING_STARTUP_ASSEMBLIES_ENV_STR          L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"
-#define HOSTING_STARTUP_ASSEMBLIES_NAME             L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES="
 #define HOSTING_STARTUP_ASSEMBLIES_VALUE            L"Microsoft.AspNetCore.Server.IISIntegration"
-#define ASPNETCORE_IIS_AUTH_ENV_STR                 L"ASPNETCORE_IIS_HTTPAUTH="
-#define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED="
-#define ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR        L"ASPNETCORE_IIS_PHYSICAL_PATH="
-#define ASPNETCORE_HTTPS_PORT_ENV_STR               L"ASPNETCORE_HTTPS_PORT="
+#define ASPNETCORE_IIS_AUTH_ENV_STR                 L"ASPNETCORE_IIS_HTTPAUTH"
+#define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED"
+#define ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR        L"ASPNETCORE_IIS_PHYSICAL_PATH"
+#define ASPNETCORE_HTTPS_PORT_ENV_STR               L"ASPNETCORE_HTTPS_PORT"
 #define ASPNETCORE_IIS_AUTH_WINDOWS                 L"windows;"
 #define ASPNETCORE_IIS_AUTH_BASIC                   L"basic;"
 #define ASPNETCORE_IIS_AUTH_ANONYMOUS               L"anonymous;"

+ 30 - 373
src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h

@@ -2,6 +2,8 @@
 // Licensed under the MIT License. See License.txt in the project root for license information.
 
 #pragma once
+#include <map>
+#include "Environment.h"
 
 class ENVIRONMENT_VAR_HELPERS
 {
@@ -24,422 +26,77 @@ public:
     }
 
     static
-    VOID
-    CopyToTable(
-        ENVIRONMENT_VAR_ENTRY *   pEntry,
-        PVOID                     pvData
-    )
-    {
-        // best effort copy, ignore the failure
-        ENVIRONMENT_VAR_ENTRY *   pNewEntry = new ENVIRONMENT_VAR_ENTRY();
-        if (pNewEntry != NULL)
-        {
-            pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue());
-            ENVIRONMENT_VAR_HASH *pHash = static_cast<ENVIRONMENT_VAR_HASH *>(pvData);
-            DBG_ASSERT(pHash);
-            pHash->InsertRecord(pNewEntry);
-            // Need to dereference as InsertRecord references it now
-            pNewEntry->Dereference();
-        }
-    }
-
-    static
-    VOID
-    AppendEnvironmentVariables
-    (
-        ENVIRONMENT_VAR_ENTRY *   pEntry,
-        PVOID                     pvData
-    )
-    {
-        HRESULT hr = S_OK;
-        DWORD   dwResult = 0;
-        DWORD   dwError = 0;
-        STRU    struNameBuffer;
-        STACK_STRU(struValueBuffer, 300);
-        BOOL    fFound = FALSE;
-
-        HRESULT* pHr = static_cast<HRESULT*>(pvData);
-
-        // pEntry->QueryName includes the trailing =, remove it before calling stru
-        if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName())))
-        {
-            goto Finished;
-        }
-        dwResult = struNameBuffer.LastIndexOf(L'=');
-        if (dwResult != -1)
-        {
-            struNameBuffer.QueryStr()[dwResult] = L'\0';
-            if (FAILED(hr = struNameBuffer.SyncWithBuffer()))
-            {
-                goto Finished;
-            }
-        }
-
-        dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), struValueBuffer.QueryStr(), struValueBuffer.QuerySizeCCH());
-        if (dwResult == 0)
-        {
-            dwError = GetLastError();
-            // Windows API (e.g., CreateProcess) allows variable with empty string value
-            // in such case dwResult will be 0 and dwError will also be 0
-            // As UI and CMD does not allow empty value, ignore this environment var
-            if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS)
-            {
-                hr = HRESULT_FROM_WIN32(dwError);
-                goto Finished;
-            }
-        }
-        else if (dwResult > struValueBuffer.QuerySizeCCH())
-        {
-            // have to increase the buffer and try get environment var again
-            struValueBuffer.Reset();
-            struValueBuffer.Resize(dwResult + (DWORD)wcslen(pEntry->QueryValue()) + 2); // for null char and semicolon
-            dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(),
-                struValueBuffer.QueryStr(),
-                struValueBuffer.QuerySizeCCH());
-
-            if (dwResult <= 0)
-            {
-                hr = HRESULT_FROM_WIN32(GetLastError());
-                goto Finished;
-            }
-            fFound = TRUE;
-        }
-        else
-        {
-            fFound = TRUE;
-        }
-
-        if (FAILED(hr = struValueBuffer.SyncWithBuffer()))
-        {
-            goto Finished;
-        }
-
-        if (fFound)
-        {
-            if (FAILED(hr = struValueBuffer.Append(L";")))
-            {
-                goto Finished;
-            }
-        }
-        if (FAILED(hr = struValueBuffer.Append(pEntry->QueryValue())))
-        {
-            goto Finished;
-        }
-
-        if (FAILED(hr = pEntry->Initialize(pEntry->QueryName(), struValueBuffer.QueryStr())))
-        {
-            goto Finished;
-        }
-
-    Finished:
-        if (FAILED(hr))
-        {
-            *pHr = hr;
-        }
-        return;
-    }
-
-    static
-    VOID
-    SetEnvironmentVariables
-    (
-        ENVIRONMENT_VAR_ENTRY *   pEntry,
-        PVOID                     pvData
-    )
-    {
-        UNREFERENCED_PARAMETER(pvData);
-        HRESULT hr = S_OK;
-        DWORD dwResult = 0;
-        STRU struNameBuffer;
-
-        HRESULT* pHr = static_cast<HRESULT*>(pvData);
-
-        // pEntry->QueryName includes the trailing =, remove it before calling SetEnvironmentVariable.
-        if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName())))
-        {
-            goto Finished;
-        }
-        dwResult = struNameBuffer.LastIndexOf(L'=');
-        if (dwResult != -1)
-        {
-            struNameBuffer.QueryStr()[dwResult] = L'\0';
-            if (FAILED(hr = struNameBuffer.SyncWithBuffer()))
-            {
-                goto Finished;
-            }
-        }
-
-        dwResult = SetEnvironmentVariable(struNameBuffer.QueryStr(), pEntry->QueryValue());
-        if (dwResult == 0)
-        {
-            hr = HRESULT_FROM_WIN32(GetLastError());
-        }
-
-    Finished:
-        if (FAILED(hr))
-        {
-            *pHr = hr;
-        }
-        return;
-    }
-
-    static
-    HRESULT
+    std::map<std::wstring, std::wstring, ignore_case_comparer>
     InitEnvironmentVariablesTable
     (
-        _In_ ENVIRONMENT_VAR_HASH*          pInEnvironmentVarTable,
+        _In_ const std::map<std::wstring, std::wstring, ignore_case_comparer>& pInEnvironmentVarTable,
         _In_ BOOL                           fWindowsAuthEnabled,
         _In_ BOOL                           fBasicAuthEnabled,
         _In_ BOOL                           fAnonymousAuthEnabled,
+        _In_ BOOL                           fAddHostingStartup,
         _In_ PCWSTR                         pApplicationPhysicalPath,
-        _In_ PCWSTR                         pHttpsPort,
-        _Out_ ENVIRONMENT_VAR_HASH**        ppEnvironmentVarTable
+        _In_ PCWSTR                         pHttpsPort
     )
     {
-        HRESULT hr = S_OK;
-        BOOL    fFound = FALSE;
-        DWORD   dwResult, dwError;
-        STRU    strIisAuthEnvValue;
-        STACK_STRU(strStartupAssemblyEnv, 1024);
-        ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL;
-        ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL;
-        ENVIRONMENT_VAR_ENTRY* pIISPathEntry = NULL;
-        ENVIRONMENT_VAR_ENTRY* pIISHttpsPort = NULL;
-        ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL;
-
-        pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH();
-
-        //
-        // few environment variables expected, small bucket size for hash table
-        //
-        if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/)))
-        {
-            goto Finished;
-        }
-
-        // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements
-        pInEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HELPERS::CopyToTable, pEnvironmentVarTable);
-        if (pEnvironmentVarTable->Count() != pInEnvironmentVarTable->Count())
-        {
-            // hash table copy failed
-            hr = E_UNEXPECTED;
-            goto Finished;
-        }
-
-        pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, &pIISPathEntry);
-        if (pIISPathEntry != NULL)
-        {
-            // user defined ASPNETCORE_IIS_PHYSICAL_PATH in configuration, wipe it off
-            pIISPathEntry->Dereference();
-            pEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR);
-        }
-
-        pIISPathEntry = new ENVIRONMENT_VAR_ENTRY();
-
-        if (FAILED(hr = pIISPathEntry->Initialize(ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, pApplicationPhysicalPath)) ||
-            FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISPathEntry)))
-        {
-            goto Finished;
-        }
-
-        if (pHttpsPort != nullptr)
-        {
-            pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_HTTPS_PORT_ENV_STR, &pIISHttpsPort);
-            if (pIISHttpsPort != NULL)
-            {
-                // user defined ASPNETCORE_HTTPS_PORT in configuration, don't override it
-                pIISHttpsPort->Dereference();
-            }
-            else
-            {
-                pIISHttpsPort = new ENVIRONMENT_VAR_ENTRY();
-
-                if (FAILED(hr = pIISHttpsPort->Initialize(ASPNETCORE_HTTPS_PORT_ENV_STR, pHttpsPort)) ||
-                    FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISHttpsPort)))
-                {
-                    goto Finished;
-                }
-            }
-        }
+        std::map<std::wstring, std::wstring, ignore_case_comparer> environmentVariables = pInEnvironmentVarTable;
 
-        pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry);
-        if (pIISAuthEntry != NULL)
+        environmentVariables.insert_or_assign(ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, pApplicationPhysicalPath);
+        if (pHttpsPort)
         {
-            // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off
-            pIISAuthEntry->Dereference();
-            pEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR);
+            environmentVariables.try_emplace(ASPNETCORE_HTTPS_PORT_ENV_STR, pHttpsPort);
         }
 
+        std::wstring strIisAuthEnvValue;
         if (fWindowsAuthEnabled)
         {
-            strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS);
+            strIisAuthEnvValue.append(ASPNETCORE_IIS_AUTH_WINDOWS);
         }
-
         if (fBasicAuthEnabled)
         {
-            strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC);
+            strIisAuthEnvValue.append(ASPNETCORE_IIS_AUTH_BASIC);
         }
-
         if (fAnonymousAuthEnabled)
         {
-            strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS);
+            strIisAuthEnvValue.append(ASPNETCORE_IIS_AUTH_ANONYMOUS);
         }
-
-        if (strIisAuthEnvValue.IsEmpty())
+        if (strIisAuthEnvValue.empty())
         {
-            strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE);
+            strIisAuthEnvValue.append(ASPNETCORE_IIS_AUTH_NONE);
         }
 
-        pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY();
+        environmentVariables.insert_or_assign(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue);
 
-        if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) ||
-            FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry)))
+        if (fAddHostingStartup && environmentVariables.count(HOSTING_STARTUP_ASSEMBLIES_ENV_STR) == 0)
         {
-            goto Finished;
-        }
+            auto hostingStartupValues = Environment::GetEnvironmentVariableValue(HOSTING_STARTUP_ASSEMBLIES_ENV_STR).value_or(L"");
 
-        // Compiler is complaining about conversion between PCWSTR and PWSTR here.
-        // Explictly casting.
-        pEnvironmentVarTable->FindKey((PWSTR)HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry);
-        if (pHostingEntry != NULL)
-        {
-            // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration
-            // the value will be used in OutputEnvironmentVariables. Do nothing here
-            pHostingEntry->Dereference();
-            pHostingEntry = NULL;
-            goto Skipped;
-        }
-
-        //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system
-        dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR,
-            strStartupAssemblyEnv.QueryStr(),
-            strStartupAssemblyEnv.QuerySizeCCH());
-        if (dwResult == 0)
-        {
-            dwError = GetLastError();
-            // Windows API (e.g., CreateProcess) allows variable with empty string value
-            // in such case dwResult will be 0 and dwError will also be 0
-            // As UI and CMD does not allow empty value, ignore this environment var
-            if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS)
-            {
-                hr = HRESULT_FROM_WIN32(dwError);
-                goto Finished;
-            }
-        }
-        else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH())
-        {
-            // have to increase the buffer and try get environment var again
-            strStartupAssemblyEnv.Reset();
-            strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1);
-            dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR,
-                strStartupAssemblyEnv.QueryStr(),
-                strStartupAssemblyEnv.QuerySizeCCH());
-            if (dwResult <= 0)
+            if (hostingStartupValues.find(HOSTING_STARTUP_ASSEMBLIES_ENV_STR) == std::wstring::npos)
             {
-                hr = E_UNEXPECTED;
-                goto Finished;
+                hostingStartupValues += std::wstring(L";") + HOSTING_STARTUP_ASSEMBLIES_VALUE;
             }
-            fFound = TRUE;
-        }
-        else
-        {
-            fFound = TRUE;
-        }
-
-        strStartupAssemblyEnv.SyncWithBuffer();
-        if (strStartupAssemblyEnv.IndexOf(HOSTING_STARTUP_ASSEMBLIES_VALUE) == -1)
-        {
-        if (fFound)
-        {
-            strStartupAssemblyEnv.Append(L";");
-        }
-        strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE);
-        }
-
-        // the environment variable was not defined, create it and add to hashtable
-        pHostingEntry = new ENVIRONMENT_VAR_ENTRY();
 
-        if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) ||
-            FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry)))
-        {
-            goto Finished;
+            environmentVariables.insert_or_assign(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, hostingStartupValues);
         }
 
-    Skipped:
-        *ppEnvironmentVarTable = pEnvironmentVarTable;
-        pEnvironmentVarTable = NULL;
-
-    Finished:
-        if (pHostingEntry != NULL)
+        for (auto& environmentVariable : environmentVariables)
         {
-            pHostingEntry->Dereference();
-            pHostingEntry = NULL;
+            environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second);
         }
 
-        if (pIISAuthEntry != NULL)
-        {
-            pIISAuthEntry->Dereference();
-            pIISAuthEntry = NULL;
-        }
-
-        if (pEnvironmentVarTable != NULL)
-        {
-            pEnvironmentVarTable->Clear();
-            delete pEnvironmentVarTable;
-            pEnvironmentVarTable = NULL;
-        }
-        return hr;
+        return environmentVariables;
     }
 
     static
-    HRESULT
+    std::map<std::wstring, std::wstring, ignore_case_comparer>
     AddWebsocketEnabledToEnvironmentVariables
     (
-        _Inout_ ENVIRONMENT_VAR_HASH*       pInEnvironmentVarTable,
+        _Inout_ const std::map<std::wstring, std::wstring, ignore_case_comparer>& pInEnvironmentVarTable,
         _In_ BOOL                           fWebsocketsEnabled
     )
     {
-        HRESULT hr = S_OK;
-        ENVIRONMENT_VAR_ENTRY* pIISWebsocketEntry = NULL;
-        STACK_STRU(strIISWebsocketEnvValue, 40);
-
-        // We only need to set the WEBSOCKET_SUPPORTED environment variable for out of process
-        pInEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, &pIISWebsocketEntry);
-        if (pIISWebsocketEntry != NULL)
-        {
-            // user defined ASPNETCORE_IIS_WEBSOCKETS in configuration, wipe it off
-            pIISWebsocketEntry->Dereference();
-            pInEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR);
-        }
-        // Set either true or false for the WebsocketEnvValue.
-        if (fWebsocketsEnabled)
-        {
-            if (FAILED(hr = strIISWebsocketEnvValue.Copy(L"true")))
-            {
-                goto Finished;
-            }
-        }
-        else
-        {
-            if (FAILED(hr = strIISWebsocketEnvValue.Copy(L"false")))
-            {
-                goto Finished;
-            }
-        }
-
-        pIISWebsocketEntry = new ENVIRONMENT_VAR_ENTRY();
-
-        if (FAILED(hr = pIISWebsocketEntry->Initialize(ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, strIISWebsocketEnvValue.QueryStr())) ||
-            FAILED(hr = pInEnvironmentVarTable->InsertRecord(pIISWebsocketEntry)))
-        {
-            goto Finished;
-        }
-
-    Finished:
-        return hr;
+        std::map<std::wstring, std::wstring, ignore_case_comparer> environmentVariables = pInEnvironmentVarTable;
+        environmentVariables.insert_or_assign(ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, fWebsocketsEnabled ? L"true" : L"false");
+        return environmentVariables;
     }
-public:
-    ENVIRONMENT_VAR_HELPERS();
-    ~ENVIRONMENT_VAR_HELPERS();
 };
 

+ 9 - 103
src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp

@@ -15,13 +15,6 @@ REQUESTHANDLER_CONFIG::~REQUESTHANDLER_CONFIG()
         delete[] m_ppStrArguments;
         m_ppStrArguments = NULL;
     }
-
-    if (m_pEnvironmentVariables != NULL)
-    {
-        m_pEnvironmentVariables->Clear();
-        delete m_pEnvironmentVariables;
-        m_pEnvironmentVariables = NULL;
-    }
 }
 
 HRESULT
@@ -100,12 +93,7 @@ REQUESTHANDLER_CONFIG::Populate(
     IAppHostElement                *pWindowsAuthenticationElement = NULL;
     IAppHostElement                *pBasicAuthenticationElement = NULL;
     IAppHostElement                *pAnonymousAuthenticationElement = NULL;
-    IAppHostElement                *pEnvVarList = NULL;
-    IAppHostElement                *pEnvVar = NULL;
-    IAppHostElementCollection      *pEnvVarCollection = NULL;
     ULONGLONG                       ullRawTimeSpan = 0;
-    ENUM_INDEX                      index;
-    ENVIRONMENT_VAR_ENTRY*          pEntry = NULL;
     DWORD                           dwCounter = 0;
     DWORD                           dwPosition = 0;
     WCHAR*                          pszPath = NULL;
@@ -114,26 +102,20 @@ REQUESTHANDLER_CONFIG::Populate(
     BSTR                            bstrAnonymousAuthSection = NULL;
     BSTR                            bstrAspNetCoreSection = NULL;
 
-    m_pEnvironmentVariables = new ENVIRONMENT_VAR_HASH();
-    if (FAILED(hr = m_pEnvironmentVariables->Initialize(37 /*prime*/)))
-    {
-        delete m_pEnvironmentVariables;
-        m_pEnvironmentVariables = NULL;
-        goto Finished;
-    }
-
     pAdminManager = pHttpServer->GetAdminManager();
-    if (pSite != nullptr)
+    try
     {
-        try
+        WebConfigConfigurationSource source(pAdminManager, *pHttpApplication);
+        if (pSite != nullptr)
         {
-            WebConfigConfigurationSource source(pAdminManager, *pHttpApplication);
             m_struHttpsPort.Copy(BindingInformation::GetHttpsPort(BindingInformation::Load(source, *pSite)).c_str());
         }
-        catch (...)
-        {
-            FINISHED_IF_FAILED(OBSERVE_CAUGHT_EXCEPTION());
-        }
+
+        m_pEnvironmentVariables = source.GetSection(CS_ASPNETCORE_SECTION)->GetMap(CS_ASPNETCORE_ENVIRONMENT_VARIABLES);
+    }
+    catch (...)
+    {
+        FINISHED_IF_FAILED(OBSERVE_CAUGHT_EXCEPTION());
     }
 
     hr = m_struConfigPath.Copy(pHttpApplication->GetAppConfigPath());
@@ -397,58 +379,6 @@ REQUESTHANDLER_CONFIG::Populate(
         goto Finished;
     }
 
-    hr = GetElementChildByName(pAspNetCoreElement,
-        CS_ASPNETCORE_ENVIRONMENT_VARIABLES,
-        &pEnvVarList);
-    if (FAILED(hr))
-    {
-        goto Finished;
-    }
-
-    hr = pEnvVarList->get_Collection(&pEnvVarCollection);
-    if (FAILED(hr))
-    {
-        goto Finished;
-    }
-
-    for (hr = FindFirstElement(pEnvVarCollection, &index, &pEnvVar);
-        SUCCEEDED(hr);
-        hr = FindNextElement(pEnvVarCollection, &index, &pEnvVar))
-    {
-        if (hr == S_FALSE)
-        {
-            hr = S_OK;
-            break;
-        }
-
-        if (FAILED(hr = GetElementStringProperty(pEnvVar,
-            CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME,
-            &strEnvName)) ||
-            FAILED(hr = GetElementStringProperty(pEnvVar,
-                CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE,
-                &strEnvValue)) ||
-            FAILED(hr = strEnvName.Append(L"=")) ||
-            FAILED(hr = STRU::ExpandEnvironmentVariables(strEnvValue.QueryStr(), &strExpandedEnvValue)))
-        {
-            goto Finished;
-        }
-
-        pEntry = new ENVIRONMENT_VAR_ENTRY();
-
-        if (FAILED(hr = pEntry->Initialize(strEnvName.QueryStr(), strExpandedEnvValue.QueryStr())) ||
-            FAILED(hr = m_pEnvironmentVariables->InsertRecord(pEntry)))
-        {
-            goto Finished;
-        }
-        strEnvName.Reset();
-        strEnvValue.Reset();
-        strExpandedEnvValue.Reset();
-        pEnvVar->Release();
-        pEnvVar = NULL;
-        pEntry->Dereference();
-        pEntry = NULL;
-    }
-
 Finished:
 
     if (pAspNetCoreElement != NULL)
@@ -475,29 +405,5 @@ Finished:
         pBasicAuthenticationElement = NULL;
     }
 
-    if (pEnvVarList != NULL)
-    {
-        pEnvVarList->Release();
-        pEnvVarList = NULL;
-    }
-
-    if (pEnvVar != NULL)
-    {
-        pEnvVar->Release();
-        pEnvVar = NULL;
-    }
-
-    if (pEnvVarCollection != NULL)
-    {
-        pEnvVarCollection->Release();
-        pEnvVarCollection = NULL;
-    }
-
-    if (pEntry != NULL)
-    {
-        pEntry->Dereference();
-        pEntry = NULL;
-    }
-
     return hr;
 }

+ 2 - 3
src/Servers/IIS/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h

@@ -68,7 +68,7 @@ public:
         _Out_ REQUESTHANDLER_CONFIG  **ppAspNetCoreConfig
     );
 
-    ENVIRONMENT_VAR_HASH*
+    std::map<std::wstring, std::wstring, ignore_case_comparer>&
     QueryEnvironmentVariables(
         VOID
     )
@@ -225,7 +225,6 @@ protected:
     //
     REQUESTHANDLER_CONFIG() :
         m_fStdoutLogEnabled(FALSE),
-        m_pEnvironmentVariables(NULL),
         m_hostingModel(HOSTING_UNKNOWN),
         m_ppStrArguments(NULL)
     {
@@ -257,7 +256,7 @@ protected:
     BOOL                   m_fBasicAuthEnabled;
     BOOL                   m_fAnonymousAuthEnabled;
     APP_HOSTING_MODEL      m_hostingModel;
-    ENVIRONMENT_VAR_HASH*  m_pEnvironmentVariables;
+    std::map<std::wstring, std::wstring, ignore_case_comparer> m_pEnvironmentVariables;
     STRU                   m_struHostFxrLocation;
     PWSTR*                 m_ppStrArguments;
     DWORD                  m_dwArgc;

+ 101 - 19
src/Servers/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs

@@ -2,50 +2,132 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
 using Microsoft.AspNetCore.Testing.xunit;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
 {
-    [Collection(IISTestSiteCollection.Name)]
-    public class EnvironmentVariableTests: FixtureLoggedTest
+    [Collection(PublishedSitesCollection.Name)]
+
+    public class EnvironmentVariableTests: IISFunctionalTestBase
     {
-        private readonly IISTestSiteFixture _fixture;
+        private readonly PublishedSitesFixture _fixture;
 
-        public EnvironmentVariableTests(IISTestSiteFixture fixture): base(fixture)
+        public EnvironmentVariableTests(PublishedSitesFixture fixture)
         {
             _fixture = fixture;
         }
 
+        [ConditionalTheory]
+        [InlineData(HostingModel.InProcess)]
+        [InlineData(HostingModel.OutOfProcess)]
+        public async Task GetLongEnvironmentVariable(HostingModel hostingModel)
+        {
+            var expectedValue = "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
+                                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
+                                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
+                                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
+                                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
+                                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative";
+
+
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_TESTING_LONG_VALUE"] = expectedValue;
+
+            Assert.Equal(
+                expectedValue,
+                await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_INPROCESS_TESTING_LONG_VALUE"));
+        }
+
         [ConditionalFact]
-        public async Task GetUniqueEnvironmentVariable()
+        [RequiresNewHandler]
+        public Task AuthHeaderEnvironmentVariableRemoved_InProcess() => AuthHeaderEnvironmentVariableRemoved(HostingModel.InProcess);
+
+        [ConditionalFact]
+        public Task AuthHeaderEnvironmentVariableRemoved_OutOfProcess() => AuthHeaderEnvironmentVariableRemoved(HostingModel.OutOfProcess);
+
+        private async Task AuthHeaderEnvironmentVariableRemoved(HostingModel hostingModel)
         {
-            Assert.Equal("foobar", await _fixture.Client.GetStringAsync("/CheckEnvironmentVariable"));
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_IIS_HTTPAUTH"] = "shouldberemoved";
+
+            Assert.DoesNotContain("shouldberemoved", await GetStringAsync(deploymentParameters,"/GetEnvironmentVariable?name=ASPNETCORE_IIS_HTTPAUTH"));
         }
 
         [ConditionalFact]
-        public async Task GetLongEnvironmentVariable()
+        [RequiresNewHandler]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigOverridesGlobalEnvironmentVariables_InProcess() => WebConfigOverridesGlobalEnvironmentVariables(HostingModel.InProcess);
+
+        [ConditionalFact]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigOverridesGlobalEnvironmentVariables_OutOfProcess() => WebConfigOverridesGlobalEnvironmentVariables(HostingModel.OutOfProcess);
+
+        private async Task WebConfigOverridesGlobalEnvironmentVariables(HostingModel hostingModel)
         {
-            Assert.Equal(
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
-                "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative",
-                await _fixture.Client.GetStringAsync("/CheckEnvironmentLongValueVariable"));
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.EnvironmentVariables["ASPNETCORE_ENVIRONMENT"] = "Development";
+            deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_ENVIRONMENT"] = "Production";
+            Assert.Equal("Production", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_ENVIRONMENT"));
+        }
+
+        [ConditionalFact]
+        [RequiresNewHandler]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigAppendsHostingStartup_InProcess() => WebConfigAppendsHostingStartup(HostingModel.InProcess);
+
+        [ConditionalFact]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigAppendsHostingStartup_OutOfProcess() => WebConfigAppendsHostingStartup(HostingModel.OutOfProcess);
+
+        private async Task WebConfigAppendsHostingStartup(HostingModel hostingModel)
+        {
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.EnvironmentVariables["ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"] = "Asm1";
+            if (hostingModel == HostingModel.InProcess)
+            {
+                Assert.Equal("Asm1", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"));
+            }
+            else
+            {
+                Assert.Equal("Asm1;Microsoft.AspNetCore.Server.IISIntegration", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"));
+            }
         }
 
         [ConditionalFact]
-        public async Task GetExistingEnvironmentVariable()
+        [RequiresNewHandler]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigOverridesHostingStartup_InProcess() => WebConfigOverridesHostingStartup(HostingModel.InProcess);
+
+        [ConditionalFact]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigOverridesHostingStartup_OutOfProcess() => WebConfigOverridesHostingStartup(HostingModel.OutOfProcess);
+
+        private async Task WebConfigOverridesHostingStartup(HostingModel hostingModel)
         {
-            Assert.Contains(";foobarbaz", await _fixture.Client.GetStringAsync("/CheckAppendedEnvironmentVariable"));
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.EnvironmentVariables["ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"] = "Asm1";
+            deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"] = "Asm2";
+            Assert.Equal("Asm2", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"));
         }
 
         [ConditionalFact]
-        public async Task AuthHeaderEnvironmentVariableRemoved()
+        [RequiresNewHandler]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigExpandsVariables_InProcess() => WebConfigExpandsVariables(HostingModel.InProcess);
+
+        [ConditionalFact]
+        [RequiresIIS(IISCapability.PoolEnvironmentVariables)]
+        public Task WebConfigExpandsVariables_OutOfProcess() => WebConfigExpandsVariables(HostingModel.OutOfProcess);
+
+        private async Task WebConfigExpandsVariables(HostingModel hostingModel)
         {
-            Assert.DoesNotContain("shouldberemoved", await _fixture.Client.GetStringAsync("/CheckRemoveAuthEnvironmentVariable"));
+            var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true);
+            deploymentParameters.EnvironmentVariables["TestVariable"] = "World";
+            deploymentParameters.WebConfigBasedEnvironmentVariables["OtherVariable"] = "%TestVariable%;Hello";
+            Assert.Equal("World;Hello", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=OtherVariable"));
         }
     }
 }

+ 6 - 0
src/Servers/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs

@@ -45,6 +45,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
             return result;
         }
 
+        protected virtual async Task<string> GetStringAsync(IISDeploymentParameters parameters, string path)
+        {
+            var result = await DeployAsync(parameters);
+            return await result.HttpClient.GetStringAsync(path);
+        }
+
         public override void Dispose()
         {
             StopServer(false);

+ 0 - 23
src/Servers/IIS/test/testassets/InProcessWebSite/Startup.cs

@@ -266,29 +266,6 @@ namespace TestSite
             }
         }
 
-        private async Task CheckEnvironmentVariable(HttpContext ctx)
-        {
-            var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_VALUE");
-            await ctx.Response.WriteAsync(variable);
-        }
-
-        private async Task CheckEnvironmentLongValueVariable(HttpContext ctx)
-        {
-            var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_LONG_VALUE");
-            await ctx.Response.WriteAsync(variable);
-        }
-
-        private async Task CheckAppendedEnvironmentVariable(HttpContext ctx)
-        {
-            var variable = Environment.GetEnvironmentVariable("ProgramFiles");
-            await ctx.Response.WriteAsync(variable);
-        }
-
-        private async Task CheckRemoveAuthEnvironmentVariable(HttpContext ctx)
-        {
-            var variable = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_HTTPAUTH");
-            await ctx.Response.WriteAsync(variable);
-        }
         private async Task ReadAndWriteSynchronously(HttpContext ctx)
         {
             var t2 = Task.Run(() => WriteManyTimesToResponseBody(ctx));

+ 0 - 6
src/Servers/IIS/test/testassets/InProcessWebSite/web.config

@@ -5,12 +5,6 @@
       <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
     </handlers>
     <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" hostingModel="inprocess">
-      <environmentVariables>
-        <environmentVariable name="ASPNETCORE_INPROCESS_TESTING_VALUE" value="foobar" />
-        <environmentVariable name="ASPNETCORE_INPROCESS_TESTING_LONG_VALUE" value="AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNativeAReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNativeAReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNativeAReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNativeAReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNativeAReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" />
-        <environmentVariable name="ProgramFiles" value="foobarbaz" />
-        <environmentVariable name="ASPNETCORE_IIS_HTTPAUTH" value="shouldberemoved" />
-      </environmentVariables>
     </aspNetCore>
   </system.webServer>
 </configuration>

+ 5 - 0
src/Servers/IIS/test/testassets/shared/SharedStartup/Startup.shared.cs

@@ -134,5 +134,10 @@ namespace TestSite
             GetDllDirectory(1024, builder);
             await context.Response.WriteAsync(builder.ToString());
         }
+
+        private async Task GetEnvironmentVariable(HttpContext ctx)
+        {
+            await ctx.Response.WriteAsync(Environment.GetEnvironmentVariable(ctx.Request.Query["name"].ToString()));
+        }
     }
 }