没有控制台 Window 或依赖项的简单 DOM 操作

Simple DOM Manipulation Without Console Window or Dependencies

我正在为用户编写一个打开文件的解决方案,这个文件应该导航到某个网站并将用户的用户名插入到他们的登录表单中。 此文件需要由 citrix 会话中的用户访问

这应该非常简单,我发现了一种通过 Powershell 实现的方法:

$aduser = Get-ADUser $env:USERNAME -Properties EmailAddress
$emailaddress = $aduser.EmailAddress

$url = "https://website.org/loginpage.asp"
$ie = New-Object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.Navigate($url)
WaitForPage 10
$ie.Document.GetElementById("USERID").Value = $emailaddress

这非常有效 - 它打开网页,并插入用户名(电子邮件地址)。

然而,当用户从他们的机器上运行它时,似乎不可能隐藏 CMD window(如果 运行 来自 .cmd.bat)作为以及 Powershell window。 -WindowStyle Hidden 只是减少了 window 出现的时间长度 - 这不是一个可接受的解决方案。

所以我的下一个行动计划是在 c# 中重新创建上述代码并将其作为 exe 分发(因为这不太可能显示任何控制台 windows)。但是,我似乎无法在不依赖外部库的 C# 中找到任何执行此操作的方法(例如 Selenium,它还需要安装驱动程序,这对我来说不是一个有效的选项)。

我想我的问题是 - 上面的 Powershell 脚本可以用 C# 重新创建吗?该脚本中的 -comobject 是 .NET 对象吗?如果是,我如何在 C# 中利用它?


供参考 - 我目前调用 .ps1 文件如下(在 CMD 文件中):

START Powershell.exe -WindowStyle Hidden -File \file\Folder\SK\scripts\powershell\opensite.ps1

而且我还没有找到任何方法来实际隐藏出现的控制台 windows。我要么需要为此找到解决方案,要么需要一种在 C# 中实现相同功能的简单方法。

You can certainly call COM objects in C#, as explained in this existing answer:

If the library is already registered, you can perform the following steps to have Visual Studio generate an interop assembly for you:

  • Open to your Visual Studio project.
  • Right click on 'References' (right under the project in your Solution Explorer) and select 'Add Reference'.
  • Select the COM tab.
  • Select the Component you wish to interop with.
  • Select 'ok'.

This will be a class or set of C# classes that wrap all of the COM interface stuff with a normal C# class. Then you just use it like any other C# library. If the import of the reference worked well, you can explore it like any other reference and the methods/structs/classes/constants should show up in that namespace and intellisense.

或者,您可以使用运行空间和管道在 C# 中执行 PowerShell。 See Runspace samples on MSDN(这里是 link 中的示例 3):

namespace Microsoft.Samples.PowerShell.Runspaces
{
  using System;
  using System.Collections;
  using System.Management.Automation;
  using System.Management.Automation.Runspaces;
  using PowerShell = System.Management.Automation.PowerShell;

  /// <summary>
  /// This class contains the Main entry point for this host application.
  /// </summary>
  internal class Runspace03
  {
    /// <summary>
    /// This sample shows how to use the PowerShell class to run a
    /// script that retrieves process information for the list of 
    /// process names passed to the script. It shows how to pass input 
    /// objects to a script and how to retrieve error objects as well 
    /// as the output objects.
    /// </summary>
    /// <param name="args">Parameter not used.</param>
    /// <remarks>
    /// This sample demonstrates the following:
    /// 1. Creating a PowerSHell object to run a script.
    /// 2. Adding a script to the pipeline of the PowerShell object.
    /// 3. Passing input objects to the script from the calling program.
    /// 4. Running the script synchronously.
    /// 5. Using PSObject objects to extract and display properties from 
    ///    the objects returned by the script.
    /// 6. Retrieving and displaying error records that were generated
    ///    when the script was run.
    /// </remarks>
    private static void Main(string[] args)
    {
      // Define a list of processes to look for.
      string[] processNames = new string[] 
      {
        "lsass", "nosuchprocess", "services", "nosuchprocess2" 
      };

      // The script to run to get these processes. Input passed
      // to the script will be available in the $input variable.
      string script = "$input | get-process -name {$_}";

      // Create a PowerShell object. Creating this object takes care of 
      // building all of the other data structures needed to run the script.
      using (PowerShell powershell = PowerShell.Create())
      {
        powershell.AddScript(script);

        Console.WriteLine("Process              HandleCount");
        Console.WriteLine("--------------------------------");

        // Invoke the script synchronously and display the   
        // ProcessName and HandleCount properties of the 
        // objects that are returned.
        foreach (PSObject result in powershell.Invoke(processNames))
        {
          Console.WriteLine(
                            "{0,-20} {1}",
                            result.Members["ProcessName"].Value,
                            result.Members["HandleCount"].Value);
        }

        // Process any error records that were generated while running 
        //  the script.
        Console.WriteLine("\nThe following non-terminating errors occurred:\n");
        PSDataCollection<ErrorRecord> errors = powershell.Streams.Error;
        if (errors != null && errors.Count > 0)
        {
          foreach (ErrorRecord err in errors)
          {
            System.Console.WriteLine("    error: {0}", err.ToString());
          }
        }
      }

      System.Console.WriteLine("\nHit any key to exit...");
      System.Console.ReadKey();
    }
  }
}

虽然第二种方法无疑会花费更长的时间,但如果您想将 PowerShell 代码保留在可执行文件之外,这样您就可以更轻松地更改它,而不必每次都重新编译,这可能是有意义的。

从本质上讲,exe 可以用于不可见地执行给定的 powershell 脚本。

正如我 ,您可以通过 wscript.exe 使用 VBScript 或 Jscript 来避免启动另一个控制台 window。这是一个示例 Jscript 脚本,编写为批处理 + Jscript 混合。用 .bat 扩展名保存它,加盐调味,试一试。

@if (@CodeSection == @Batch) @then
@echo off & setlocal

wscript /e:JScript "%~f0"

goto :EOF
@end // end batch / begin JScript hybrid code

var user = WSH.CreateObject("ADSystemInfo"),
    email = GetObject("LDAP://" + user.UserName).EmailAddress,
    url = "https://website.org/loginpage.asp",
    ie = WSH.CreateObject('InternetExplorer.Application');

ie.visible = true;
ie.Navigate(url);
while (ie.readyState != 4) WSH.Sleep(25);

ie.document.getElementById('USERID').value = email;

if (ie.document.getElementById('password'))
    ie.document.getElementById('password').focus();

它实际上是一个多语言脚本,因为您可以使用 .bat 扩展名或 .js 保存它。作为 .js,您可以双击它并启动(假设 .js 文件与 wscript.exe 相关联,因为它们通常默认情况下是这样的)根本没有任何控制台 window。


如果您更喜欢 .exe 文件,可以很容易地将上面的脚本修改为可以自编译的脚本和 link Jscript.NET 可执行文件。 (此脚本仍具有 .bat 扩展名。)

@if (@CodeSection == @Batch) @then
@echo off & setlocal

for /f "delims=" %%I in ('dir /b /s "%windir%\microsoft.net\*jsc.exe"') do (
    if not exist "%~dpn0.exe" "%%~I" /nologo /target:winexe /out:"%~dpn0.exe" "%~f0"
)
"%~dpn0.exe"
goto :EOF
@end // end batch / begin JScript.NET hybrid code

import System;
import System.Diagnostics;

try {
    var wshShell:Object = new ActiveXObject("Wscript.Shell"),
        user:Object = new ActiveXObject("ADSystemInfo"),
        email:String = GetObject("LDAP://" + user.UserName).EmailAddress,
        url:String = "https://website.org/loginpage.asp",
        ie:Object = new ActiveXObject('InternetExplorer.Application');
}
catch(e:Exception) { System.Environment.Exit(1); }

ie.visible = true;
ie.Navigate(url);

// force IE window to the foreground and give it focus
var proc:System.Diagnostics.Process[] = System.Diagnostics.Process.GetProcesses();
for (var i:Int16 = proc.length, hwnd:IntPtr = IntPtr(ie.hwnd); i--;) {
    if (proc[i].MainWindowHandle === hwnd && wshShell.AppActivate(proc[i].Id)) break;
}

while (ie.readyState != 4) System.Threading.Thread.Sleep(25);
ie.document.getElementById('USERID').value = email;
if (ie.document.getElementById('password'))
    ie.document.getElementById('password').focus();

在我正确理解 rojo 的回答之前,我一直在使用下面的 C#。这种方法确实有效,但不如 simple/elegant rojo 的方法,所以我将其标记为答案。

static class Program
{
    private static void Main()
    {
        var startInfo = new ProcessStartInfo(@"\file\administration\Unused\Apps\opencascade\Alternate.bat")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        Process.Start(startInfo);

        var startInfo = new ProcessStartInfo("Wscript.exe", @"\file\administration\Unused\Apps\opencascade\Alternate.js")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        Process.Start(startInfo);

        var startInfo = new ProcessStartInfo("Powershell.exe",
            @"\file\administration\Unused\Apps\opencascade\opencascade.ps1")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        Process.Start(startInfo);
    }
}

注意这里的主要方法是运行3种不同的解法,一个接一个。不同的 "versions" 取决于使用 .bat.ps1.js 在环境中是否更有意义。我将使用 .js 版本,它在此上下文中工作,并且此 C# 代码生成的可执行文件隐藏了生成的控制台 window.