Repository: pythonnet/pythonnet (language: python)

Issue:
System.ArgumentException: Illegal characters in path
### Environment

-   Pythonnet version: 3.0.3
-   Python version: 3.10.11
-   Operating System: Windows 10
-   .NET Runtime: 4.5

### Details

-   Trying to load DLL using AddReference. 

import clr
clr.AddReference("C:\MyProject\TestFolder\_Dependencies\MyDLLProject\Test.TA.MyVersion.Test.dll")
  
System.ArgumentException: Illegal characters in path.
   at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
   at System.IO.Path.Combine(String path1, String path2)
   at Python.Runtime.AssemblyManager.<FindAssemblyCandidates>d__15.MoveNext()
   at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
   at Python.Runtime.AssemblyManager.LoadAssemblyPath(String name)
   at Python.Runtime.CLRModule.AddReference(String name)


DLL is not a new implementation. Same DLL and same path was working fine with python 3.7 and PythonNET 2.4.0. This issue started happening when we upgraded python and Pythonnet versions. 



Discussion / hints:
This is not a support forum. If you are using backslashes in strings in Python (and really, almost all programming languages) you have to either escape them or use a raw string literal.In Python that would be `r"blabla\blubb"`.
Sorry it was a typo. Yes, I have escaped the back slash. Its nothing to do with backslash. Because same code works fine when tested with python 3.7 and PythonNET 2.4.0.
Tried to load the same DLL using Assembly.LoadFrom(path) - this works without any issues. But clr.AddReference(path) throws Illegal characters in path exception.

from System.Reflection import Assembly
assembly = Assembly.LoadFrom(path)
@manju1847 can you share your sys.path? I suspect we stumble on something funky there.
echo %PATH%
C:\Program Files\IcedTeaWeb\WebStart\bin;C:\Program Files\Zulu\zulu-8-jre\bin\;C:\Program Files (x86)\Zulu\zulu-8-jre\bin\;C:\Program Files\Python310\Scripts\;C:\Program Files\Python310\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python37-32\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\dotnet\;C:\Users\fnh3651\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;C:\Program Files\Microsoft VS Code\bin;C:\Users\fnh3651\AppData\Local\Programs\Python\Python310\Scripts\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python310\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python312\Scripts\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python312\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python37-32\Scripts\;C:\Users\fnh3651\AppData\Local\Programs\Python\Python37-32\;C:\Users\fnh3651\AppData\Local\Microsoft\WindowsApps;C:\Users\fnh3651\.dotnet\tools;C:\TrueBeam\PyWinAuto_HAL\_Dependencies\TDSConfigManager;
@manju1847 no, `sys.path` of Python.

(client_env) C:\Automation\Main\drop_hal_pywinauto\TA_HAL>python --version
Python 3.10.11

(client_env) C:\Automation\Main\drop_hal_pywinauto\TA_HAL>where python
C:\Automation\Main\drop_hal_pywinauto\client_env\Scripts\python.exe
C:\Users\svc-pa-autbuild\AppData\Local\Programs\Python\Python310\python.exe
C:\Users\svc-pa-autbuild\AppData\Local\Microsoft\WindowsApps\python.exe

(client_env) C:\Automation\Main\drop_hal_pywinauto\TA_HAL>python
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.path)
['', 'C:\\Automation\\Main\\drop_hal_pywinauto\\TA_HAL\\"TA_HAL"', 'C:\\Users\\svc-pa-autbuild\\AppData\\Local\\Programs\\Python\\Python310\\python310.zip', 'C:\\Users\\svc-pa-autbuild\\AppData\\Local\\Programs\\Python\\Python310\\DLLs', 'C:\\Users\\svc-pa-autbuild\\AppData\\Local\\Programs\\Python\\Python310\\lib', 'C:\\Users\\svc-pa-autbuild\\AppData\\Local\\Programs\\Python\\Python310', 'C:\\Automation\\Main\\drop_hal_pywinauto\\client_env', 'C:\\Automation\\Main\\drop_hal_pywinauto\\client_env\\lib\\site-packages', 'C:\\Automation\\Main\\drop_hal_pywinauto\\client_env\\lib\\site-packages\\win32', 'C:\\Automation\\Main\\drop_hal_pywinauto\\client_env\\lib\\site-packages\\win32\\lib', 'C:\\Automation\\Main\\drop_hal_pywinauto\\client_env\\lib\\site-packages\\Pythonwin']
>>>

The first line seems suspicious. There are quotes in directory name. These are impossible on Windows.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adef224e0..23184258d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
 ### Fixed
 
 -   Fixed RecursionError for reverse operators on C# operable types from python. See #2240
+-   Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376
 
 ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11
 
diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs
index a8bbd1f6c..82658bf50 100644
--- a/src/runtime/AssemblyManager.cs
+++ b/src/runtime/AssemblyManager.cs
@@ -200,6 +200,13 @@ static IEnumerable<string> FindAssemblyCandidates(string name)
                 }
                 else
                 {
+                    int invalidCharIndex = head.IndexOfAny(Path.GetInvalidPathChars());
+                    if (invalidCharIndex >= 0)
+                    {
+                        using var importWarning = Runtime.PyObject_GetAttrString(Exceptions.exceptions_module, "ImportWarning");
+                        Exceptions.warn($"Path entry '{head}' has invalid char at position {invalidCharIndex}", importWarning.BorrowOrThrow());
+                        continue;
+                    }
                     path = Path.Combine(head, name);
                 }
 
diff --git a/src/runtime/Exceptions.cs b/src/runtime/Exceptions.cs
index da095e030..85e56eace 100644
--- a/src/runtime/Exceptions.cs
+++ b/src/runtime/Exceptions.cs
@@ -270,7 +270,7 @@ public static void warn(string message, BorrowedReference exception, int stackle
             }
 
             using var warn = Runtime.PyObject_GetAttrString(warnings_module.obj, "warn");
-            Exceptions.ErrorCheck(warn.Borrow());
+            warn.BorrowOrThrow();
 
             using var argsTemp = Runtime.PyTuple_New(3);
             BorrowedReference args = argsTemp.BorrowOrThrow();
@@ -283,7 +283,7 @@ public static void warn(string message, BorrowedReference exception, int stackle
             Runtime.PyTuple_SetItem(args, 2, level.StealOrThrow());
 
             using var result = Runtime.PyObject_CallObject(warn.Borrow(), args);
-            Exceptions.ErrorCheck(result.Borrow());
+            result.BorrowOrThrow();
         }
 
         public static void warn(string message, BorrowedReference exception)


Test patch:
diff --git a/tests/test_module.py b/tests/test_module.py
index ddfa7bb36..0c20dcfc0 100644
--- a/tests/test_module.py
+++ b/tests/test_module.py
@@ -344,6 +344,20 @@ def test_clr_add_reference():
     with pytest.raises(FileNotFoundException):
         AddReference("somethingtotallysilly")
 
+
+def test_clr_add_reference_bad_path():
+    import sys
+    from clr import AddReference
+    from System.IO import FileNotFoundException
+    bad_path = "hello\0world"
+    sys.path.append(bad_path)
+    try:
+        with pytest.raises(FileNotFoundException):
+            AddReference("test_clr_add_reference_bad_path")
+    finally:
+        sys.path.remove(bad_path)
+
+
 def test_clr_get_clr_type():
     """Test clr.GetClrType()."""
     from clr import GetClrType