VBA - WScript.Shell - 执行 PowerShell 命令

VBA - WScript.Shell - Executing A PowerShell Command

我想使用 WScript.Shell

从 VBA 执行 PowerShell 命令

这是发送到 WScript.Shell 的字符串:

Powershell -ExecutionPolicy Bypass -Command $FilesInPathway = get-childitem -path "C:\Users\Me\Desktop\NewFolder" -recurse -attributes !directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace(".TXT",".txt"); Rename-Item $file.fullname $tempName}

这是我要执行的 PowerShell 命令。请注意,此命令确实在 PowerShell.

中成功执行
$FilesInPathway = get-childitem -path "C:\Users\Me\Desktop\NewFolder" -recurse -attributes !directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace(".TXT",".txt"); Rename-Item $file.fullname $tempName}

未获取执行 PowerShell 命令时出现问题的信息,但命令未执行且文件未重命名。 Msgbox returns 没有信息。空白表格。希望避免切换到 PowerShell 脚本或使用 DIR 和 NAME 编写此脚本。

strCommand = "Powershell -ExecutionPolicy Bypass -Command $FilesInPathway = get-childitem -path " & """" & dataPacket.filePath & """" & " -recurse -Attributes !Directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace(" & """" & dataPacket.stringOld & """" & "," & """" & dataPacket.stringNew & """" & "); Rename-Item $file.fullname $tempName}"
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
strOutput = WshShellExec.StdOut.ReadAll
MsgBox strOutput

更新我

新命令确实遍历了目录树并且returns正确了该树中的文件数。一定是我传递的 foreach 函数没有执行。但是当我直接在 Powershell 中 运行 debug.print 版本的字符串时,它执行正常 - 所有文件都被重命名。

strCommand = "Powershell $FilesInPathway = get-childitem -path " & """" & dataPacket.filePath & """" & " -recurse -attributes !directory; $FilesInPathway.count"
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
Do While WshShellExec.Status = 0
     Application.Wait (Now() + TimeValue("0:00:01"))
Loop
strOutput = WshShellExec.StdOut.ReadAll
MsgBox strOutput

传递给Wsh的字符串Shell:

Powershell $FilesInPathway = get-childitem -path "C:\Users\Me\Desktop\NewFolder" -recurse -attributes !directory; $FilesInPathway.count

传递给Power的字符串Shell:

$FilesInPathway = get-childitem -path "C:\Users\Me\Desktop\NewFolder" -recurse -attributes !directory; $FilesInPathway.count

请注意,我曾尝试使用 StdIn.Write 方法来隔离命令,但出现错误。

WshShellExec.StdIn.Write "$FilesInPathway.count"

更新二

运行 Shell() 声明的类型或枚举可能会生成恶意宏警告并杀死 excel 应用程序?尚未隔离导致此问题的代码。

如果命令中包含-noexit,则以下将进入无限循环 当 WshShellExec.Status = 0 时执行 Application.Wait (Now() + TimeValue("0:00:01")) 循环

回答

当然这是语法问题。需要用 ' ' 将 Replace 方法的参数括起来。如果文件路径包含空格,它也需要用“”括起来。如果直接输入到 PowerShell,带“ ”的附件工作正常。所以命令是:

strCommand = "Powershell $FilesInPathway = get-childitem -path " & "'" & dataPacket.filePath & "'" & " -recurse -attributes !directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace('" & dataPacket.stringOld & "','" & dataPacket.stringNew & "'); Rename-Item $file.fullname $tempName}"

这似乎可以解释原因:

1.等到脚本执行完毕

你必须等到 WshShell.Exec 完成执行,因为它不会向你发出信号。所以你的代码继续前进,因为脚本仍然是 运行,它没有返回响应,你的 MessageBox 保持为空。

最简单的方法是使用WshShell.Run方法,因为它提供了一个等待执行完成的参数。

strCommand = "Powershell -ExecutionPolicy Bypass -Command $FilesInPathway = get-childitem -path " & """" & dataPacket.filePath & """" & " -recurse -Attributes !Directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace(" & """" & dataPacket.stringOld & """" & "," & """" & dataPacket.stringNew & """" & "); Rename-Item $file.fullname $tempName}"
Set WshShell = CreateObject("WScript.Shell")
strOutput = WshShell.Run(strCommand ,0 ,True)
MsgBox strOutput

也可以等待.Exec方法完成(WshShellExec.Status <> 0),通过循环有时检查.Status

strCommand = "Powershell -ExecutionPolicy Bypass -Command $FilesInPathway = get-childitem -path " & """" & dataPacket.filePath & """" & " -recurse -Attributes !Directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace(" & """" & dataPacket.stringOld & """" & "," & """" & dataPacket.stringNew & """" & "); Rename-Item $file.fullname $tempName}"
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
Do While WshShellExec.Status = 0
     Application.Wait(Now() + TimeValue("0:00:01"))
Loop
strOutput = WshShellExec.StdOut.ReadAll
MsgBox strOutput

2。您不能从 shell 传递多个 powershell 命令(没有一些技巧,请阅读 Docs

只需使用命令创建一个 ps1 文件,然后像上面那样执行该脚本。

这将使您能够使用 Powershell IDE 进行编码,并且您可以在其他地方重用该脚本! 所有动态代码都可以作为参数传递给您在脚本中创建的匹配参数(另存为 RenameFiles.ps1)。

param ($FilePath, $StringOld, $StringNew)
$FilesInPathway = get-childitem -path $FilePath -recurse -Attributes !Directory 
foreach ($file in $FilesInPathway)
{
    $tempName = $file.name 
    $tempName = $tempName.replace($StringOld,$StringNew) 
    Rename-Item $file.fullname $tempName
}

执行该脚本的命令:

strCommand = "Powershell -ExecutionPolicy Bypass -File ""path\to\RenameFiles.ps1"" """ & dataPacket.filePath & """ """ & dataPacket.stringOld & """ """ & dataPacket.stringeNew & """"

@learnAsWeGo 我不想 post 这作为答案(请参阅我之前的评论)但无法将其标记为代码并将其添加到评论中。就是说,如果这适合您,那么您可以专注于编辑 strPoshLine,以便它执行您想要的操作。可能是您的替代选择。

Dim strPoshLine As String
Dim Retval As Variant

'Example1 to execute a powershell line from VBA and write host output to a file
    strPoshLine = "get-childitem -directory | Select-object fullname | out-file 'c:\PoshFromVbaTeat.txt'"
    Retval = Shell("Powershell.exe -noexit -Command " & strPoshLine, 0)

'Example2 to execute a powershell line from VBA and not exit the session so
    strPoshLine = "get-childitem -directory | Select-object fullname "
    Retval = Shell("Powershell.exe -noexit -Command " & strPoshLine, 1)
    MsgBox "Enter 'exit' in powershell window to close the session."

@learnAsWeGo,根据您的意见,以下代码可能更适合您。 -Filter 只能使用 * 和 ?但是 -replace 表达式可以使用正则表达式。打开会话 window 中的输出显示重命名的文件。输出可以通过管道传输到格式命令以满足您的喜好。您可以查看输出并在完成后退出 window。

Public Sub VbaToPwsh()

Dim strGetFiles As String
Dim strRename As String
Dim varRetval As Variant

strGetFiles = "Get-Childitem -Path 'C:\zDev\*' -Filter '*.txt' -Recurse -File "
' Notes:  If using -Recurse then the last '\*' in the path is not needed, otherwise needed if using -Filter.
'         The use of -Filter and -File is just to reduce the number of files going through the pipeline
'           and not needed if the -replace operator provides all the constraint required.

strRename = " Rename-Item -NewName {$_.Name -replace '.txt', '.TXT'} -PassThru "
' Notes:  The -replace comparison operator is not case sensitive on selection but writes as shown.
'         If case sensitivity on selection is needed use -creplace instead.
'         Rename-Item does not provide output and that is way the -PassThru param is added.
          
varRetval = Shell("Powershell.exe -noexit -Command " & strGetFiles & "|" & strRename, 1)

MsgBox "Enter 'exit' in PowerShell window to close the session."

End Sub

当然这是一个语法问题。需要用 ' ' 将 Replace 方法的参数括起来。如果文件路径包含空格,它也需要用“”括起来。如果直接输入到 PowerShell,带有“”的附件工作正常。所以命令是:

strCommand = "Powershell $FilesInPathway = get-childitem -path " & "'" & dataPacket.filePath & "'" & " -recurse -attributes !directory; foreach ($file in $FilesInPathway){$tempName = $file.name; $tempName = $tempName.replace('" & dataPacket.stringOld & "','" & dataPacket.stringNew & "'); Rename-Item $file.fullname $tempName}"

这似乎可以解释原因: