创建PowerShell变量|通过Python子进程创建和删除本地用户



我想创建一个新的本地用户,并能够使用子流程通过PyQt5应用程序删除本地用户。我将让用户在QLineEdits中输入新本地用户的信息。

我发现了几个类似于我自己的Stack Overflow问题,但没有一个与我试图实现的目标完全相关。

当然,我在谷歌上搜索了这个话题,发现了一些我尝试过但仍然没有成功的信息。

我尝试过的代码如下所示:

account_type = ""Local Administrator Account""
user_name = ""DIdaho""
full_name = ""Duncan Idaho""
user_password = ""password""

对于上面的变量,我将字符串放在双引号中,就像我在PowerShell中编写的脚本一样,如下所示:

# PowerShell provides a text box to enter the password which obviously is stored in the $Password variable using this line
$Password = Read-Host -AsSecureString
New-LocalUser “DIdaho” -Password $Password -FullName “Duncan Idaho” Description “Local Administrator Account”
Add-LocalGroupMember -Group “Administrators” -Member “DIdaho”

我已经编写了Python脚本,以便在subprocess.run((中尽可能接近PowerShell脚本。但是,我完全不确定我是否可以在subprocess中声明和初始化PowerShell变量,或者我做得是否正确。我尝试了三种不同的方法,如下所示:

user_password = subprocess.run([“Powershell.exe”, “$Password = Read-Host -AsSecureString”], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
subprocess.run([“Powershell.exe”, “$Password = Read-Host -AsSecureString”], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
subprocess.Popen([“Powershell.exe”, “$Password = Read-Host -AsSecureString”] , stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True) )

我也不确定Python变量是否在子流程中工作。run/Popen((。我使用的代码如下:

# Strings placed in double quotes
account_type = ""Loacal Administrator Account""
user_name = ""DIdaho""
full_name = ""Amiri Baraka""
local_group = ""Administrators""
subprocess.run(["Powershell.exe", "$Password = Read-Host -AsSecureString"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True) 
subprocess.run(["Powershell.exe", "New-LocalUser", f"{user_name}", "-Password $Password”, “-FullName {full_name}", f"-Description {account_type}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
subprocess.run(["Powershell.exe", "Add-LocalGroupMember", "-Group", f"{local_group}", "-Member", f"{user_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
# Strings placed in double quotes
account_type = ""Loacal Administrator Account""
user_name = ""DIdaho""
full_name = ""Amiri Baraka""
local_group = ""Administrators""
user_password = ""password""
subprocess.run(["Powershell.exe", "$Password = Read-Host -AsSecureString"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True) 
subprocess.run(["Powershell.exe", "New-LocalUser", f"{user_name}", f"-Password $Password", f"-FullName {full_name}", f"-Description {account_type}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
subprocess.run(["Powershell.exe", "Add-LocalGroupMember", "-Group", f"{local_group}", "-Member", f"{user_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
# Strings placed in double quotes
account_type = ""Loacal Administrator Account""
user_name = ""DIdaho""
full_name = ""Amiri Baraka""
local_group = ""Administrators""
# I tried  Popen believing it would display the text box like it does when working in PowerShell itself.
subprocess.Popen(["Powershell.exe", "$Password = Read-Host -AsSecureString"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True) 
subprocess.run(["Powershell.exe", "New-LocalUser", f"{user_name}", "-Password $Password”, “-FullName {full_name}", f"-Description {account_type}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)
subprocess.run(["Powershell.exe", "Add-LocalGroupMember", "-Group", f"{local_group}", "-Member", f"{user_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, shell=True)

当尝试删除用户帐户时,应用程序什么也不做。单击按钮并转到"本地用户和组"后,用户帐户仍然存在。PowerShell脚本和Python脚本如下:

PowerShell:

Remove-LocalUser -Name “DIdaho”

Python:

subprocess.run([“Powershell.exe”, “Remove-LocalUser”, f“-Name {user_name}”], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,  shell=True)

PYQt5 GUI应用程序示例:

from PyQt5.QtWidgets import *
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
import sys
import subprocess

class Window(QWidget):
def __init__(self):
super().__init__()
self.resize(850, 200)
self.setWindowTitle("Create New Local User")
self.setFont(QtGui.QFont("Arial", 14))
self.gui()
def gui(self):
self.widgets()
self.layouts()
self.show()
def widgets(self):
self.user_name_ldt = QLineEdit()
self.user_name_ldt.setPlaceholderText("Enter Username")
self.user_password_ldt = QLineEdit()
self.user_password_ldt.setPlaceholderText("Enter User Password")
self.user_password_ldt.setEchoMode(QLineEdit.Password)
self.full_name_ldt = QLineEdit()
self.full_name_ldt.setPlaceholderText("Enter Full Name")
self.account_description_ldt = QLineEdit()
self.account_description_ldt.setPlaceholderText("Enter Account Description")
self.local_group_lbl = QLabel("Local Group:")
# Define a List initialized with the available Groups
self.local_groups = ["--None--", "Administrators", "Guest", "Human Resources", "Marketing", "Security"]
self.local_group_cbx = QComboBox()
# Populate the local_groups_cbx combobox with the account_types List items
for local_group in self.local_groups:
self.local_group_cbx.addItem(local_group)
self.create_user_btn = QPushButton("Create User")
self.create_user_btn.clicked.connect(self.create_new_local_user)
def layouts(self):
self.main_layout = QVBoxLayout()
self.row_one_layot = QHBoxLayout()
self.row_two_layot = QHBoxLayout()
self.row_three_layout = QHBoxLayout()
self.row_four_layout = QHBoxLayout()
self.form_layout = QFormLayout()
self.form_layout.setSpacing(15)
self.form_layout.setAlignment(Qt.AlignCenter)
self.row_one_layot.addWidget(self.local_group_lbl)
self.row_one_layot.addWidget(self.local_group_cbx)
self.row_one_layot.addStretch()
self.row_two_layot.addWidget(self.user_name_ldt)
self.row_two_layot.addWidget(self.user_password_ldt)
self.row_three_layout.addWidget(self.full_name_ldt)
self.row_three_layout.addWidget(self.account_description_ldt)
# self.row_four_layout.addStretch()
self.row_four_layout.addWidget(self.create_user_btn)
self.form_layout.addRow(self.row_one_layot)
self.form_layout.addRow(self.row_two_layot)
self.form_layout.addRow(self.row_three_layout)
self.form_layout.addRow(self.row_four_layout)
self.main_layout.addLayout(self.form_layout)
self.main_layout.setContentsMargins(10, 20, 10, 20)
self.setLayout(self.main_layout)
def create_new_local_user(self):
account_description = self.account_description_ldt.text()
user_name = self.user_name_ldt.text()
full_name = self.full_name_ldt.text()
local_group = self.local_group_cbx.currentText()
user_password = self.user_password_ldt.text()
if local_group == self.local_groups[0]:
msgbox = QMessageBox.warning(self, "Null Value", "Please select a Group.")
else:
print(account_description)
print(user_name)
print(full_name)
print(local_group)
print(user_password)
commands = f'''
$Password = Read-Host -AsSecureString
New- LocalUser {user_name} -Password $Password -FullName "{full_name}" -Description "{account_description}"
Add-LocalGroupMember -Group {local_group} -Member {user_name}
'''
subprocess.run(['Powershell.exe', '-NoProfile', '-Command', commands], stdout=subprocess.PIPE)

def main():
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec_())

if __name__ == '__main__':
main()

powershell.exe使用单个调用,并将所有命令传递给-Command(-c(参数[1](多个呼叫,除了效率低下之外,在不共享任何状态的不相关子进程中运行(:

  • 为了概念清晰,请将PowerShell代码作为单个参数传递给-Command,并根据需要使用嵌入引用。

  • 为了便于引用和清晰的格式,通过多行f-string(Python 3.6+(构造该参数:

import subprocess
# Values to embed in the PowerShell code.
# Note that they are NOT placed in embedded double quotes here - 
# that is handled below.
account_type = "Local Administrator Account"
user_name = "DIdaho"
full_name = "Duncan Idaho"
local_group = "Administrators"
# Construct the PowerShell commands with string interpolation,
# *as a single string*.
# Use embedded "..." quoting as needed.
commands = f'''
$Password = Read-Host -AsSecureString
New-LocalUser {user_name} -Password $Password -FullName "{full_name}" -Description "{account_type}"
Add-LocalGroupMember -Group {local_group} -Member {user_name}
'''
# Print prompt text for the Read-Host call that the PowerShell
# command will perform - see remarks below the code.
print('Please enter a password for the new user:')
# Run a powershell.exe subprocess with the commands and capture its
# stdout output (adjust as needed)
# Note: Do NOT use shell=True - doing so would needlessly involve cmd.exe 
#       and introduce syntax pitfalls.
proc = subprocess.run(
[
'powershell.exe',
'-Command',
commands 
],
stdout=subprocess.PIPE
)
# Print captured stdout output.
print(proc.stdout.decode())

为了避免不必要的开销并确保可预测的运行时环境,请考虑将'-NoProfile',放在'-Command',之前

注:

  • 与您的原始代码一样,上述操作会使PowerShell提示输入密码,这总是发生在控制台窗口中。

    • 使用Pythonprint()调用来提前打印提示消息的原因是,由于使用stdout=subprocess.PIPE捕获PowerShell命令的输出,您试图通过Read-Host本身显示的任何提示文本都将成为捕获输出的一部分,因此仅在命令完成后显示
    • 还要注意,捕获的输出将始终包含用户响应Read-Host调用键入的密码的掩码表示(例如****(
  • 您稍后的更新显示,您正在创建一个GUI应用程序,在该应用程序中,可能不希望PowerShell控制台提示输入密码;有两种解决方案,尽管请注意,它们都涉及以纯文本的形式将密码通信到PowerShell进程,这比从PowerShell中使用Read-Host -AsSecureString更不安全:

    • 解决方案A:通过stdin将密码传递给PowerShell进程,PowerShell进程将自动响应Read-Host调用。有关使用subprocess.Popen()Popen.communicate()将stdin输入发送到子进程的示例,请参阅此答案。

    • 许多CI/CD工具使用的解决方案B:将密码定义为Python应用程序中的环境变量(例如os.environ['__p'] = 'foo',PowerShell可以使用(代替Read-Host调用(查询并转换为安全字符串

      ConvertTo-SecureString -AsPlainText -Force $env:__p
      
    • 注意:虽然将密码作为嵌入PowerShell命令字符串的参数传递(通过字符串插值(在技术上也有效,但这是最不安全的选项,因为任何可以查询子进程命令行字符串的人都可以看到纯文本密码。


[1]注意:对于powershell.exe-Command(-c(是隐含的,但为了概念清晰,最好是显式的,尤其是因为对于现代跨平台PowerShell(Core(版本的CLIpwsh-File(-f(现在是默认值

最新更新