@@ -28,9 +28,17 @@ final class Config
2828 */
2929 public static function loadSystemConfigBlocking ()
3030 {
31- // Use WMIC output on Windows
31+ /* Use WMIC or PowerShell on Windows
32+ * WMIC is faster where available, but was removed in Windows 11 24H2+
33+ * PowerShell is slower, but is available on all Windows versions
34+ */
3235 if (DIRECTORY_SEPARATOR === '\\' ) {
33- return self ::loadWmicBlocking ();
36+ $ config = self ::loadWmicBlocking ();
37+ if ($ config ->nameservers ) {
38+ return $ config ;
39+ }
40+
41+ return self ::loadPowershellBlocking ();;
3442 }
3543
3644 // otherwise (try to) load from resolv.conf
@@ -100,6 +108,55 @@ public static function loadResolvConfBlocking($path = null)
100108 return $ config ;
101109 }
102110
111+ /**
112+ * Loads the DNS configurations using Windows PowerShell
113+ *
114+ * Note that this method blocks while loading the given command and should
115+ * thus be used with care! While this should be relatively fast for normal
116+ * PowerShell commands, it remains unknown if this may block under certain
117+ * circumstances. In particular, this method should only be executed before
118+ * the loop starts, not while it is running.
119+ *
120+ * Note that this method will only try to execute the given command and try to
121+ * parse its output, irrespective of whether this command exists. In
122+ * particular, this method requires the DnsClient module which is only available
123+ * on Windows 8/Server 2012 and later. Currently, this will only parse valid
124+ * nameserver entries from the command output and will ignore all other output
125+ * without complaining.
126+ *
127+ * Note that the previous section implies that this may return an empty
128+ * `Config` object if no valid nameserver entries can be found.
129+ *
130+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
131+ * @return self
132+ * @link https://learn.microsoft.com/en-us/powershell/module/dnsclient/get-dnsclientserveraddress
133+ */
134+ public static function loadPowershellBlocking ($ command = null )
135+ {
136+ $ contents = shell_exec ($ command === null ? 'powershell -NoLogo -NoProfile -NonInteractive -Command "Get-DnsClientServerAddress | Select-Object -ExpandProperty ServerAddresses" ' : $ command );
137+
138+ $ config = new self ();
139+ if ($ contents !== null ) {
140+ foreach (explode ("\n" , $ contents ) as $ line ) {
141+ $ ip = trim ($ line );
142+ if ($ ip === '' || @inet_pton ($ ip ) === false ) {
143+ continue ;
144+ }
145+
146+ // skip Windows placeholder DNS addresses (fec0:0:0:ffff::1, ::2, ::3)
147+ // these are added to interfaces without explicit DNS configuration and don't resolve anything
148+ if (preg_match ('/^fec0:0:0:ffff::/i ' , $ ip )) {
149+ continue ;
150+ }
151+
152+ $ config ->nameservers [] = $ ip ;
153+ }
154+ $ config ->nameservers = array_values (array_unique ($ config ->nameservers ));
155+ }
156+
157+ return $ config ;
158+ }
159+
103160 /**
104161 * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
105162 *
@@ -113,19 +170,23 @@ public static function loadResolvConfBlocking($path = null)
113170 * parse its output, irrespective of whether this command exists. In
114171 * particular, this command is only available on Windows. Currently, this
115172 * will only parse valid nameserver entries from the command output and will
116- * ignore all other output without complaining.
173+ * ignore all other output swithout complaining.
174+ *
175+ * Note that WMIC has been deprecated and removed in recent Windows versions
176+ * (Windows 11 24H2+). Consider using loadPowershellBlocking() instead.
117177 *
118178 * Note that the previous section implies that this may return an empty
119179 * `Config` object if no valid nameserver entries can be found.
120180 *
121181 * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
122182 * @return self
123183 * @link https://ss64.com/nt/wmic.html
184+ * @deprecated WMIC is deprecated on Windows, use loadPowershellBlocking() instead
124185 */
125186 public static function loadWmicBlocking ($ command = null )
126187 {
127- $ contents = shell_exec ($ command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV ' : $ command );
128- preg_match_all ('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i ' , $ contents , $ matches );
188+ $ contents = shell_exec ($ command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV 2>nul ' : $ command );
189+ preg_match_all ('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i ' , $ contents ?? '' , $ matches );
129190
130191 $ config = new self ();
131192 $ config ->nameservers = $ matches [1 ];
0 commit comments