在批处理文件中映射网络驱动器而不硬编码驱动器号

18

我需要使用批处理文件映射网络驱动器,但不想指定驱动器字母。

批处理文件用作部署过程的一部分; 我从 CruiseControl.Net 调用批处理文件,批处理文件需要映射一个需要凭据进行身份验证的 UNC 路径。然后批处理文件调用 RoboCopy 将网站从输出目录部署到目标(并排除一些文件和文件夹)。最后,批处理文件删除网络驱动器。

问题在于,这种方式不具有可伸缩性,当只有少数几个项目时很好,但现在我们有 20 个项目使用此方法,并且正在用尽驱动器字母。我不想重新使用驱动器字母,因为它们可能会发生冲突 - 这将是糟糕的。

以下是批处理文件的示例:

@echo off
net use x: \\192.168.0.1\Share\wwwroot\MyProject /user:mydomain\myuser MyP455w0rd
robocopy.exe "W:\wwwroot\MyProject" x:\ *.* /E /XO /XD "App_Data/Search" "*.svn" /XF "sitefinity.log" "Thumbs.db" /NDL /NC /NP
net use x: /delete

并且为了易读性而格式化:

@echo off
net use x: \\192.168.0.1\Share\wwwroot\MyProject 
    /user:mydomain\myuser MyP455w0rd
robocopy.exe "W:\wwwroot\MyProject" x:\ *.* /E /XO /XD 
    "App_Data/Search" "*.svn" /XF "sitefinity.log" "Thumbs.db" /NDL /NC /NP
net use x: /delete
7个回答

38
如果您没有同时连接多个网络共享,可以使用net use *为您分配一个空闲的驱动器号。之后,您可以使用robocopy通过UNC路径访问该共享,并使用net use * /delete释放所连接的任何共享。类似这样的操作:
@echo off
net use * \\192.168.0.1\Share\wwwroot\MyProject /user:mydomain\myuser MyP455w0rd
robocopy.exe "W:\wwwroot\MyProject" "\\192.168.0.1\Share\wwwroot\MyProject" *.* /E /XO /XD "App_Data/Search" "*.svn" /XF "sitefinity.log" "Thumbs.db" /NDL /NC /NP
net use * /delete /yes

编辑:

从一些研究中我了解到,您可以直接映射共享文件夹,而无需分配驱动器字母。这样它将通过其远程UNC路径匿名映射。通过这种方式,您也可以只指定其远程名称来删除映射。

这应该可以工作:

@echo off
net use \\192.168.0.1\Share\wwwroot\MyProject /user:mydomain\myuser MyP455w0rd
robocopy.exe "W:\wwwroot\MyProject" "\\192.168.0.1\Share\wwwroot\MyProject" *.* /E /XO /XD "App_Data/Search" "*.svn" /XF "sitefinity.log" "Thumbs.db" /NDL /NC /NP
net use \\192.168.0.1\Share\wwwroot\MyProject /delete

1
有可能同时连接多个网络共享,否则我可以为每个项目始终分配Z:或硬编码的字母。 net use * /delete也会释放所有已连接的共享资源,这将是服务器上的问题。 - Simon Martin
这比我的解决方案好多了 :) - Simon Martin
1
“net use.../delete”似乎不能立即清除凭证。针对经过身份验证的位置运行robocopy,在调用该过程直到进程退出后仍然有效。 [klist purge](https://serverfault.com/a/500237/172060)似乎可以解决问题,但可能有些过头了。 - jpmc26

12

我使用这个方法让.NET自动选择一个可用的驱动器字母,然后使用.NET查询它所分配的字母:

net use * \\server\share
for /f "tokens=2" %%i in ('net use ^| find "\\server\share"') do set netdrive=%%i
echo %netdrive% has been mapped

8

以下批处理文件可能会对某些人有用。

它不依赖于外部程序。

批处理文件包含一个名为 :freedrive 的函数,可找到空闲的驱动器字母并将其返回到一个变量中。它还可以正确地检测没有介质的光驱占用的驱动器字母。

@echo off
setlocal

call :freedrive mydriveletter && goto :cont
echo ERROR: No free drive letter found.
goto :exit
:cont
echo Found drive letter: %mydriveletter%

goto :exit

rem Finds a free drive letter.
rem
rem Parameters:
rem     %1 = Output variable name.
rem
rem Example:
rem     call :freedrive mydriveletter && goto :cont
rem     echo ERROR: No free drive letter found.
rem     goto :EOF
rem     :cont
rem     echo Found drive letter: %mydriveletter%
:freedrive
setlocal EnableDelayedExpansion
set exitcode=0
set "output_var=%~1"
for %%i in (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) do (
    set "drive=%%i:"
    rem If 'subst' fails, the drive letter is already in use.
    rem This way we can even detect optical drives that have a drive
    rem letter but no media in them, a case that goes undetected when
    rem using 'if exist'.
    subst !drive! %SystemDrive%\ >nul
    if !errorlevel! == 0 (
        subst !drive! /d >nul
        goto :freedrive0
    )
)
set exitcode=1
set drive=
:freedrive0
endlocal & set "%output_var%=%drive%" & exit /b %exitcode%

:exit
pause

我喜欢这种方法;不是“检查可用性并使用它”,而是“使用它并查看是否有效”。 - Ben

3
一种可能的解决方案是使用pushd命令应用于确定存在的UNC路径,这样将创建一个临时驱动器字母,指向该路径。可以确定当前目录,即临时驱动器的根目录,以获取驱动器字母。最后,必须使用popd命令来删除临时驱动器字母:
pushd "\\%COMPUTERNAME%\ADMIN$"
rem /* Do stuff here with the root of the temporarily created drive
rem    used as the current working directory... */
rem // Store the current drive in variable `DRIVE` for later use:
for /F "delims=:" %%D in ("%CD%") do set "DRIVE=%%D:"
popd
echo The next free drive letter is `%DRIVE%`.

变量 COMPUTERNAME 和共享目录 \\%COMPUTERNAME%\ADMIN$ 应该在所有现代(基于NT)的Windows系统上存在。


如果您不想让当前环境通过尝试 pushd 来生成一个空闲驱动器字母而暂时“污染”,您可以采用以下方法:

for /F "delims=:" %%D in ('
    pushd "\\%COMPUTERNAME%\ADMIN$" ^& ^
        for %%Z in ^(.^) do popd
') do set "DRIVE=%%D:"
echo The next free drive letter is `%DRIVE%`.

2
好的...这可能不太华丽,但这是我现在正在使用的基本尝试捕获方法。尝试映射驱动器,如果正在使用,则进入下一步。我用两个尝试来说明这一点,但很容易将其扩展到4、10或更多个驱动器字母。
是的,这确实冒犯了我的编程感觉,我不喜欢代码的重复。不幸的是,我不知道如何将路径和凭据传递给批处理文件,因为我自己不调用它,CruiseControl.net无参数地调用它。
@echo off

:START
net use z: \\192.168.0.1\Share\wwwroot\MyProject /user:mydomain\myuser MyP455w0rd
if %ERRORLEVEL% ==2 goto Y
ROBOCOPY HERE
net use z: /delete
exit

:Y
net use y: \\192.168.0.1\Share\wwwroot\MyProject /user:mydomain\myuser MyP455w0rd
if %ERRORLEVEL% ==2 goto W
ROBOCOPY HERE
net use y: /delete
exit

:W
sleep 20
goto START

1
最简单的方法:

pushd "\\server\share\wwwroot\My Project"
robocopy . x:\ *.* /E ...etc
popd

Pushd会自动将网络驱动器映射到一个可用的盘符,popd会将其删除。它们是内部CMD命令;使用UNC路径需要启用扩展。批处理示例:

@echo off
pushd %1
  echo. --- Processing %1 as "%cd%"
  echo. ...
  rem do thing here
  echo. Done.
popd

注意:在使用命令行参数的这个实现中,命令行上的路径必须加引号。
示例会话:
E:\temp>.\demo-pushd.bat "\\server\share\wwwroot\My Project"
 --- Processing "\\server\share\wwwroot\My Project" as "X:\wwwroot\My Project"
    ...
    Done.

E:\temp> net use X:
The network connection could not be found.

More help is available by typing NET HELPMSG 2250.
E:\temp>

无法根据 OP 的要求定义凭证。 - Thomas Weller
1
@thomas:今天这里的6个答案中只有2个展示了凭据的使用,然而这是唯一一个值得点踩的答案?使用pushd而不是For循环对于它所解决的问题部分来说更加直接和简单。 - matt wilkie
1
其他答案使用net命令。虽然它们没有说明如何使用凭据,但至少是可能的。 - Thomas Weller

0

@longneck,我使用类似这样的东西:

for /f "tokens=2" %%i in ('net use * \\server\share') do (
    if defined netdrive goto :cont
    set netdrive=%%i
)

:cont
echo.%netdrive% has been mapped
pause

net use %netdrive% /d /y
pause

这样可以避免第二次通过find管道调用net。

您还可以扩展此功能,以便不直接调用set netdrive,而是在子程序中执行。这使您可以进行更多的错误检查或处理。

for /f "tokens=2" %%i in ('net use * \\server\share') do (
    if defined netdrive goto :cont
    call :parse %%i
)

:parse
if ~%1==%1~ goto :eof
if Z:==%1 (
    set netdrive=%1
)
goto :eof

:cont
echo.%netdrive% has been mapped
pause

net use %netdrive% /d /y
pause

这里的解析子程序并不是非常有用,只是为了说明概念而已。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接