首页 > 解决方案 > PowerShell 设置驱动器标签在重启前保持不变

问题描述

我们的软件需要根据用户登录的数据库映射网络驱动器。

该软件首先检查驱动器是否尚未映射,如果是,则跳过映射步骤。

如果驱动器没有被映射,或者它被映射到不同的共享(即用户之前登录到不同的数据库),那么它会清除任何现有的驱动器映射,并映射所需的驱动器。

它通过生成然后运行 ​​PowerShell 脚本来实现这一点。

Remove-SmbMapping -LocalPath "R:" -Force -UpdateProfile; 
Remove-PSDrive -Name "R" -Force; 
net use "R" /delete /y; 

$password = ConvertTo-SecureString -String "Password" -AsPlainText -Force; 
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "Username", $password; 
New-PSDrive -Name "R" -PSProvider "FileSystem" -Root "\\server\share" -Credential $credential -Persist;

$a = New-Object -ComObject shell.application;
$a.NameSpace( "R:" ).self.name = "FriendlyName";

前三行删除该驱动器号上的任何现有映射。从理论上讲,它们都做同样的事情,但是由于微软,哪条线实际起作用是完全随机的。只有当所有三行都运行时,它才能始终如一地工作。

中间三行映射新驱动器。

最后两行将新驱动器的驱动器标签更改为比默认的 \\server\share 标签更易于使用的名称

重新启动后有人第一次登录时,上述脚本运行良好。新驱动器已映射,标签已更改。

但是,如果用户随后注销并登录到不同的数据库,则标签不会更改。

例如,用户首先登录到“Database A”,驱动器映射为“DatabaseAFiles”标签。一切都很好。

但是,如果用户随后注销并登录到“数据库 B”,则驱动器已正确映射并指向正确的共享,但标签仍然显示“DatabaseAFiles”而不是“DatabaseBFiles”。

但是,如果用户重新启动他们的 PC 并登录到“数据库 B”,则标签将正确显示“数据库 BFiles”,但任何后续再次登录到其他数据库都不会更改标签。

Reboot
Log in to Database A, label is DatabaseAFiles
Log out and into Database B, label is still DatabaseAFiles
Reboot
Log in to Database B, label is now DatabaseBFiles

这不依赖于存在的最后两个脚本行(设置标签的两个),我实际上添加了这些以尝试解决此问题。如果这两行被删除,标签是默认的 \\server\share 标签,并且仍然没有正确更改,即

Reboot
Log in to Database A, label is \\servera\sharea
Log out and into Database B, label is still \\servera\sharea
Reboot
Log in to Database B, label is now \\serverb\shareb

无论标签如何,驱动器始终正确映射到正确的共享,并且使用它具有所有正确的目录和文件。

一切正常,只是每次重启后第一次登录后标签不正确。

该脚本从创建的 PowerShell 实例中的 C# 程序中运行

using (PowerShell PowerShellInstance = PowerShell.Create())
{
    PowerShellInstance.AddScript(script);

    IAsyncResult result = PowerShellInstance.BeginInvoke();

    while (result.IsCompleted == false)
    {
        Thread.Sleep(1000);
    }
}

当它映射驱动器时,它不能在管理员模式下运行(驱动器不会为实际用户映射),它必须在正常模式下运行,因此需要提前检查。

如果我获取脚本的副本并在 C# 程序之外的 PowerShell 会话中运行它,我会得到完全相同的结果(一切正常,但第一次登录后标签错误),所以它不是从内部运行C#程序。

问题完全可能出在文件资源管理器或 Windows 上,当然,将标签缓存在某处并重用它可能是问题所在。

有人对我可以尝试的事情有任何建议吗?

标签: c#powershelldictionarydrive

解决方案


这并不理想,有一点我不满意,但这是迄今为止我能想到的最好的。如果我想出更好的东西,我会改为发布。

首先,我检查是否已经有一个驱动器映射到我要使用的字母:-

// Test if mapping already exists for this database
var wrongMapping = false;
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
    var driveLetter = drive.RootDirectory.ToString().Substring(0, 1);
    if (driveLetter == mappingDetails.DriveLetter && Directory.Exists(drive.Name))
    {
        wrongMapping = true; // Assume this is the wrong drive, if not we'll return from the method before it's used anyway
        var unc = "Unknown";
        using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + driveLetter))
        {
            if (key != null)
            {
                unc = key.GetValue("RemotePath").ToString();
            }
        }
        if (unc == mappingDetails.Root)
        {
            View.Status = @"Drive already mapped to " + mappingDetails.DriveLetter + ":";
            ASyncDelay(2000, () => View.Close());
            return; // Already mapped, carry on with login
        }
    }
}

如果我们已经将正确的路径映射到正确的驱动器号,那么我们返回并跳过其余的映射代码。

如果没有,我们将有变量wrongMapping ,如果我们有一个不同的路径映射到我们想要的驱动器号,这将是真的。这意味着我们需要先取消映射该驱动器。

这是通过 C# 程序运行的 Powershell 脚本完成的,其中包含我不满意的部分:-

Remove-PSDrive mappingDetails.DriveLetter;
Remove-SmbMapping -LocalPath "mappingDetails.DriveLetter:" -Force -UpdateProfile;
Remove-PSDrive -Name "mappingDetails.DriveLetter" -Force;
net use mappingDetails.DriveLetter /delete /y;
Stop-Process -ProcessName explorer;

前四行是取消映射驱动器的不同方法,其中至少有一个可以工作。哪个工作似乎是随机的,但在所有四个驱动器之间(到目前为止)总是未映射。

然后我们得到这个位:

Stop-Process -ProcessName explorer;

这将关闭并重新启动资源管理器进程,从而迫使 Windows 承认我们刚刚取消映射的驱动器真的消失了。如果没有这个,Windows 将不会完全释放驱动器,最烦人的是它会记住驱动器标签并将其应用于下一个映射的驱动器(因此映射到 CompanyBShare 仍然是 CompanyAShare)。

但是,这样做会关闭所有打开的文件资源管理器窗口,并且还会短暂地空白任务栏,这是不好的。

但是,鉴于目前没有公司网站拥有超过一个份额,只有开发人员和支持人员需要删除现有驱动器并映射新驱动器,现在我们将忍受它。

一旦取消映射任何旧驱动器,我们就会继续并映射新驱动器,这又是通过从 C# 代码运行的 PowerShell 脚本完成的。

$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";

第一部分映射驱动器:

$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;

中间部分直接改名:

$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';

最后部分更改了注册表中的名称:

New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";

首先,它为此路径创建一个密钥(如果密钥已经存在,它将失败但脚本将继续执行)

然后它删除现有的属性_LabelFromReg(如果它不存在,它会失败,但脚本会继续)

然后它(重新)使用新的友好名称创建属性_LabelFromReg

所以,再次以两种方式做同样的事情,但在两者之间它是有效的。

我想找到一些替代方法来杀死并重新启动资源管理器进程,这真的很俗气,但它似乎是让 Windows 确认更改的唯一方法。

至少我现在在映射时在驱动器上得到了正确的标签。


推荐阅读