Sander
Sander

Reputation: 25

Make HTML file open in default browser instead of text editor in Windows batch file

I have a script that generates some HTML pages, when this is done I open the index file of the generated pages. To do this I have this code:

if exist "generated_pages/index.html" start "" "generated_pages/index.html"

Right now the page opens in my default text editor for .html files, how can I make sure it opens in the user's default browser? I don't want to use commands for specific browsers as I don't know what the user's default browser will be.

Upvotes: 0

Views: 2135

Answers (2)

rojo
rojo

Reputation: 24466

Unfortunately, there's no way to specify with the start command what type of program you wish to launch. It will launch the default associated program based on the file's extension, and you're at the ill-fated mercy of the user's dubious choice of file association for .html files. If you want to ensure your file gets opened only by a web browser and not by a text editor, then it'd be better to pass a URL into start than a filesystem location. Using an http address as an argument to start should guarantee that the thing opening the location will be a web browser.

Serving your .html file over http can be done without relying on 3rd party binaries. It's not prohibitively difficult to use .Net methods to create a rudimentary web server and serve the web page back over localhost. That way you can start "" "http://localhost:port/" and you'll have a much better chance of avoiding opening the file in a text editor if your users have screwed up their file associations.

Save the following sorcery as a .bat script, tweak the html file name and location as needed, and give it a try.

<# : httptest.bat -- https://stackoverflow.com/a/53689025/1683264
@echo off & setlocal

if exist test.html call :display test.html
goto :EOF

:display <htmlfile>
setlocal
set "infile=%~f1"
powershell -noprofile "iex (${%~f0} | out-string)"
endlocal & exit /b

: end Batch / begin PowerShell polyglot code #>
$tcpClient = new-object Net.Sockets.TcpClient
while ($port = get-random -min 1024 -max 65535) {
    try {$tcpClient.Connect("localhost", $port)}
    catch {$tcpClient.Dispose(); break}
}
$endpoint = new-object Net.IPEndPoint([Net.IPAddress]::Any, $port)
$listener = new-object Net.Sockets.TcpListener $endpoint
$listener.start()
cmd /c start "" "http://localhost:$($port)/"
$client = $listener.AcceptTcpClient()
$stream = $client.GetStream()
if ($stream.CanRead) {
    [void]$stream.read((new-object byte[] 1024), 0, 1024);
}
if ($stream.CanWrite) {
    $content = "HTTP/1.1 200 OK`n`n$(gc $env:infile)"
    $out = [text.encoding]::UTF8.GetBytes($content)
    $stream.write($out, 0, $out.length)
}
$stream.close()
$stream.dispose()
$listener.stop()

As a side benefit, serving your html over http can help you avoid tripping some browsers' security prohibiting JavaScript from executing from file:/// URLs.


If you want to include other referenced files, such as images, css files, sourced JavaScript files, etc, then that does get a little more tricky. Here's a more thorough example that listens for an initial http request for up to 60 seconds, then continues serving relative-path sourced files as the browser requests them until no requests have been received for 5 seconds. It should properly announce mime types of images and other sourced files. If you need a longer timeout, change the serve-content 5 line near the bottom.

<# : httptest2.bat -- https://stackoverflow.com/a/53689025/1683264
@echo off & setlocal

if exist "%~1" (call :display "%~1") else goto usage
goto :EOF

:usage
echo Usage: %~nx0 htmlfile
exit /b

:display <htmlfile>
setlocal
set "infile=%~f1"
powershell -noprofile "iex (${%~f0} | out-string)"
endlocal & exit /b

: end Batch / begin PowerShell polyglot code #>
Add-Type -as System.Web
$rootpath = (get-item $env:infile).DirectoryName
$filename = (get-item $env:infile).Name
$webname = [Web.HttpUtility]::UrlEncode($filename)
$tcpClient = new-object Net.Sockets.TcpClient
while ($port = get-random -min 1024 -max 65535) {
    try {$tcpClient.Connect("localhost", $port)}
    catch {$tcpClient.Dispose(); break}
}
cmd /c start "" "http://localhost:$($port)/$webname"

function log($polarity, $txt) {
    $color = (("red","darkgray"),("green","white"))[$polarity]
    write-host -nonewline "[" -f $color[1]
    write-host -nonewline "*" -f $color[0]
    write-host "] $txt" -f $color[1]
}

function serve-content($seconds) {
    $timer = (get-date).AddSeconds($seconds)
    while (!$listener.Pending()) {
        start-sleep -milliseconds 10
        if ((get-date) -ge $timer) { return $false }
    }
    $client = $listener.AcceptTcpClient()
    $stream = $client.GetStream()
    if ($stream.CanRead) {
        $request = new-object byte[] 1024
        $size = $stream.read($request, 0, $request.length)
        $headers = [text.encoding]::UTF8.GetString($request, 0, $size)
        if ($stream.CanWrite) {
            $loc = $headers.split("`r?`n")[0] -replace "^\S+\s+|\s+HTTP/\d.+$"
            $loc = $loc -replace "^/", "$rootpath/" -replace "/", "\"
            $loc = [Web.HttpUtility]::UrlDecode($loc)
            if ($loc) {
                if (!(test-path $loc -type leaf)) {
                    $loc = [Web.HttpUtility]::UrlDecode($loc)
                }
                if (test-path $loc -type leaf) {
                    $response = ,"HTTP/1.1 200 OK"
                    $mime = [Web.MimeMapping]::GetMimeMapping($loc)
                    $response += ,"Content-Type: $mime"
                    $response += ,"Content-Length: $((gi $loc).length)","",""
                    $out = [text.encoding]::UTF8.GetBytes(($response -join "`n"))
                    [byte[]]$body = gc $loc -enc byte
                    $out += $body
                    $stream.write($out, 0, $out.length)
                    log $true $loc
                }
                else {
                    $response = "HTTP/1.1 404 Not Found","",@"
<html lang="en">
    <head>
        <title>Error 404</title>
    </head>
    <body>
        <h3>Not Found</h3>
        <p>The requested resource could not be located.</p>
    </body>
</html>
"@
                    $out = [text.encoding]::UTF8.GetBytes(($response -join "`n"))
                    $stream.write($out, 0, $out.length)
                    log $false $loc
                }
            }
        }
    }
    $stream.close()
    $stream.dispose()
    $client.close()
    return $true
}

$endpoint = new-object Net.IPEndPoint([Net.IPAddress]::Any, $port)
$listener = new-object Net.Sockets.TcpListener $endpoint
$listener.start()

[void](serve-content 60)
while ((serve-content 5)) {}
$listener.stop()

Upvotes: 1

double-beep
double-beep

Reputation: 5504

I don't know if this works to other browsers, but for Chrome, this works just fine. You can open your html file with chrome.exe just like the following:

if exist "generated_pages/index.html" start "" "full\path\to\chrome.exe" file:///C:/example/generated_pages/index.html

Another way, would be to change the default handler for html files in your Windows Account. (right-click to file => Open With... => select your browser and option "Open always html files...").

Upvotes: 0

Related Questions