ADDITIONAL SYSTEM INFORMATION :
Tested on Windows 10 / 11
Tested with Java 8 / 11 / 17 / 21 / 24
A DESCRIPTION OF THE PROBLEM :
The JRE is technically able to list available shares on a UNC server.
But this functionality is not available through public API
You can only do it using `sun.awt.shell` classes via reflection.
Here some experiments I made where `\\wsl$\Ubuntu-20.04` is an available share, and `\\wsl$\dummy` is a non-existing share. Except for the second `dummy` case using `\\wsl$` and `\\fileserver.company.com` behave exactly the same.
This works to list the contents of a given share.
Tthis is a baseline.
All further experiments try to or do list the shares on the server.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04"), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces an empty result.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04").getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces an empty result.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$"), false);
System.out.println(Arrays.toString(shares));
```
This correctly lists the shares on all versions.
But it requires to know an existing share from which to start.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
File[] shares = fsv.getFiles(((File) getShellFolder.invoke(null, new File("\\\\wsl$\\Ubuntu-20.04"))).getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get`.
```java
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
getShellFolder.invoke(null, new File("\\\\wsl$"));
```
On all versions this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
```java
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
getShellFolder.invoke(null, new File("\\\\wsl$\\dummy"));
```
On all versions for `\\wsl$` this works.
On all versions for `\\fileserver.company.com` this produces `java.io.FileNotFoundException: File \\fileserver.company.com\dummy not found` due to `Win32ShellFolder2.parseDisplayName0` throwing `java.io.IOException: Could not parse name`.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
createShellFolder.setAccessible(true);
Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
win32ShellFolderManager2Constructor.setAccessible(true);
File[] shares = fsv.getFiles(((File) createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$\\dummy"))).getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On all versions this produces `java.io.FileNotFoundException: File \\wsl$ not found` due to `file.getCanonicalPath()` throwing `java.io.IOException`.
```java
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
createShellFolder.setAccessible(true);
Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
win32ShellFolderManager2Constructor.setAccessible(true);
createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$"));
```
This works for all cases on all versions and is the "best" work-around I found so far, besides heavily using internal classes and methods.
```java
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method getDesktop = win32ShellFolderManager2.getDeclaredMethod("getDesktop");
getDesktop.setAccessible(true);
Object desktop = getDesktop.invoke(null);
Class<?> win32ShellFolder2 = Class.forName("sun.awt.shell.Win32ShellFolder2");
Method parseDisplayName = win32ShellFolder2.getDeclaredMethod("parseDisplayName", String.class);
parseDisplayName.setAccessible(true);
Long pidl = (Long) parseDisplayName.invoke(desktop, "\\\\wsl$");
if (pidl != 0)
try {
Method createShellFolderFromRelativePIDL = win32ShellFolderManager2.getDeclaredMethod("createShellFolderFromRelativePIDL", win32ShellFolder2, long.class);
createShellFolderFromRelativePIDL.setAccessible(true);
File server = (File) createShellFolderFromRelativePIDL.invoke(null, desktop, pidl);
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(server, false);
System.out.println(Arrays.toString(shares));
} finally {
Method releasePIDL = win32ShellFolder2.getDeclaredMethod("releasePIDL", long.class);
releasePIDL.setAccessible(true);
releasePIDL.invoke(null, pidl);
}
```
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
- It should be possible to list available shares on a UNC server using public API.
- Path API like `Paths.get`, `Path.of`, ... should work for UNC paths without share and return proper results for exists checks and alike
- File API like `exists`, `getCanonicalPath`, ... should work for UNC paths witout share
ACTUAL -
Reflection and usage of internal classes paired with `--add-opens java.desktop/sun.awt.shell=ALL-UNNAMED` is necessary to get the desired result currently.
Tested on Windows 10 / 11
Tested with Java 8 / 11 / 17 / 21 / 24
A DESCRIPTION OF THE PROBLEM :
The JRE is technically able to list available shares on a UNC server.
But this functionality is not available through public API
You can only do it using `sun.awt.shell` classes via reflection.
Here some experiments I made where `\\wsl$\Ubuntu-20.04` is an available share, and `\\wsl$\dummy` is a non-existing share. Except for the second `dummy` case using `\\wsl$` and `\\fileserver.company.com` behave exactly the same.
This works to list the contents of a given share.
Tthis is a baseline.
All further experiments try to or do list the shares on the server.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04"), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces an empty result.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04").getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces an empty result.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(new File("\\\\wsl$"), false);
System.out.println(Arrays.toString(shares));
```
This correctly lists the shares on all versions.
But it requires to know an existing share from which to start.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
File[] shares = fsv.getFiles(((File) getShellFolder.invoke(null, new File("\\\\wsl$\\Ubuntu-20.04"))).getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On Java 8 this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get`.
```java
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
getShellFolder.invoke(null, new File("\\\\wsl$"));
```
On all versions this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
```java
Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
getShellFolder.invoke(null, new File("\\\\wsl$\\dummy"));
```
On all versions for `\\wsl$` this works.
On all versions for `\\fileserver.company.com` this produces `java.io.FileNotFoundException: File \\fileserver.company.com\dummy not found` due to `Win32ShellFolder2.parseDisplayName0` throwing `java.io.IOException: Could not parse name`.
```java
FileSystemView fsv = FileSystemView.getFileSystemView();
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
createShellFolder.setAccessible(true);
Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
win32ShellFolderManager2Constructor.setAccessible(true);
File[] shares = fsv.getFiles(((File) createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$\\dummy"))).getParentFile(), false);
System.out.println(Arrays.toString(shares));
```
On all versions this produces `java.io.FileNotFoundException: File \\wsl$ not found` due to `file.getCanonicalPath()` throwing `java.io.IOException`.
```java
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
createShellFolder.setAccessible(true);
Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
win32ShellFolderManager2Constructor.setAccessible(true);
createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$"));
```
This works for all cases on all versions and is the "best" work-around I found so far, besides heavily using internal classes and methods.
```java
Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
Method getDesktop = win32ShellFolderManager2.getDeclaredMethod("getDesktop");
getDesktop.setAccessible(true);
Object desktop = getDesktop.invoke(null);
Class<?> win32ShellFolder2 = Class.forName("sun.awt.shell.Win32ShellFolder2");
Method parseDisplayName = win32ShellFolder2.getDeclaredMethod("parseDisplayName", String.class);
parseDisplayName.setAccessible(true);
Long pidl = (Long) parseDisplayName.invoke(desktop, "\\\\wsl$");
if (pidl != 0)
try {
Method createShellFolderFromRelativePIDL = win32ShellFolderManager2.getDeclaredMethod("createShellFolderFromRelativePIDL", win32ShellFolder2, long.class);
createShellFolderFromRelativePIDL.setAccessible(true);
File server = (File) createShellFolderFromRelativePIDL.invoke(null, desktop, pidl);
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] shares = fsv.getFiles(server, false);
System.out.println(Arrays.toString(shares));
} finally {
Method releasePIDL = win32ShellFolder2.getDeclaredMethod("releasePIDL", long.class);
releasePIDL.setAccessible(true);
releasePIDL.invoke(null, pidl);
}
```
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
- It should be possible to list available shares on a UNC server using public API.
- Path API like `Paths.get`, `Path.of`, ... should work for UNC paths without share and return proper results for exists checks and alike
- File API like `exists`, `getCanonicalPath`, ... should work for UNC paths witout share
ACTUAL -
Reflection and usage of internal classes paired with `--add-opens java.desktop/sun.awt.shell=ALL-UNNAMED` is necessary to get the desired result currently.