[Work Automation] Create Game Console App (VSCode + PowerShell + Qt for Python)

Windows work automation development history :
  • Windows PowerShell (Windows PowerShell 5.1, .NET Framework)
  • PowerShell Core (PowerShell 7, .NET Core 3.1)

The following is an introduction to using PowerShell and Qt in VSCode :

Environment Build

Windows Environment Settings

setup.ps1
# following power shell script must be run on administrator

# enable administrator and set password
net user administrator [password] /active:yes

# ------------------------------

# after loging administrator account

# set execution policy for run *.ps1 using Invoke-Command
Set-ExecutionPolicy RemoteSigned

# setup WinRM
winrm quickconfig -Force
Enable-PSRemoting -Force -SkipNetworkProfileCheck
Set-Item wsman:localhost\client\trustedhosts -Force -Value *

Deploy

deploy.ps1
param([String]$ip="")
$username = "administrator"
$password = "*********"
$source = "C:\DStudio\GameConsole\"
$destination = "C:\DStudio\GameConsole\"

$secureStringPwd = $password | ConvertTo-SecureString -AsPlainText -Force
$creds = New-Object PSCredential -ArgumentList $username,$secureStringPwd
$Session = New-PSSession -ComputerName $ip -Credential $creds
Invoke-Command -Session $Session -ScriptBlock {Remove-Item -Path $args[0] -Recurse} -ArgumentList $destination
Copy-Item -Path $source -Destination $destination -ToSession $Session -Recurse

Create a Game Console App
  • Design User Interface

    Qt Creator

    New project > Application (Qt for Python) > Qt for Python - Window (UI file)


    > Location > Details > Define Class > Base class = "QWidget"

    // Do NOT select "QDialog" or "QMainWindow" to base class, else *.ui will load failed by default code.

    Done. Like this.


  • User Interface Control

    Visual Studio Code

    Buttons event binding :

    main.py
    # Buttons Event
    widget.pushButton_StartVideo.clicked.connect(lambda:buttonEvent("StartVideo"))
    widget.pushButton_StopVideo.clicked.connect(lambda:buttonEvent("StopVideo"))
    widget.pushButton_StartGame.clicked.connect(lambda:buttonEvent(widget.comboBox_GameName.currentIndex()))
    widget.pushButton_StopGame.clicked.connect(lambda:buttonEvent("StopGame"))
    

    Run powershell script :

    main.py
    def buttonEvent(name):    
    
        def getPsCode(var):
            return{
                "StartVideo": "StartVideo.ps1",
                "StopVideo": "StopVideo.ps1",
    
                0: "StartGame.ps1", #GameNameA
                1: "StartGame.ps1", #GameNameB
                2: "StartGame.ps1", #GameNameC            
    
                "StopGame": "StopGame.ps1",
            }.get(var,"error") #'error' default return
        
        def getGameName(var):
            return{
                0: "GameNameA",
                1: "GameNameB",
                2: "GameNameC",            
            }.get(var,"error") #'error' default return
    
        print(getGameName(name))
        path = os.path.join(os.path.dirname(__file__), getPsCode(name))
        cmd = ["powershell.exe", "-ExecutionPolicy", "RemoteSigned", "-File", path, "-playerNum", widget.comboBox_PlayerNum.currentText(), "-gameName", getGameName(name)]
        subprocess.Popen(cmd)
    
    All :
    
    main.py
    # This Python file uses the following encoding: utf-8
    import sys
    import os
    import subprocess
    
    
    from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
    from PySide2.QtCore import QFile
    from PySide2.QtUiTools import QUiLoader
    import PySide2.QtXml
    
    
    def buttonEvent(name):    
    
        def getPsCode(var):
            return{
                "StartVideo": "StartVideo.ps1",
                "StopVideo": "StopVideo.ps1",
    
                0: "StartGame.ps1", #GameNameA
                1: "StartGame.ps1", #GameNameB
                2: "StartGame.ps1", #GameNameC            
    
                "StopGame": "StopGame.ps1",
            }.get(var,"error") #'error' default return
        
        def getGameName(var):
            return{
                0: "GameNameA",
                1: "GameNameB",
                2: "GameNameC",            
            }.get(var,"error") #'error' default return
    
        print(getGameName(name))
        path = os.path.join(os.path.dirname(__file__), getPsCode(name))
        cmd = ["powershell.exe", "-ExecutionPolicy", "RemoteSigned", "-File", path, "-playerNum", widget.comboBox_PlayerNum.currentText(), "-gameName", getGameName(name)]
        subprocess.Popen(cmd)
    
    if __name__ == "__main__":        
        app = QApplication([])
    
        # Load UI
        ui_file_name = "form.ui"
        path = os.path.join(os.path.dirname(__file__), ui_file_name)
        ui_file = QFile(path)
    
        if not ui_file.open(QFile.ReadOnly):
            print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
            sys.exit(-1)
    
        loader = QUiLoader()
        window = loader.load(ui_file)
        ui_file.close()
        window.show()
    
        # Buttons Event
        window.startVideo.clicked.connect(lambda:buttonEvent("StartVideo"))
        window.stopVideo.clicked.connect(lambda:buttonEvent("StopVideo"))
        window.pushButton_StartGame.clicked.connect(lambda:buttonEvent(window.comboBox_GameName.currentIndex()))
        window.pushButton_StopGame.clicked.connect(lambda:buttonEvent("StopGame"))
    
        sys.exit(app.exec_())

  • Task Automation

    computers.txt
    192.168.1.101
    192.168.1.102
    192.168.1.103

    startGame.ps1
    param([Int32]$playerNum, [String]$gameName)
    
    
    function SelectPlayer {
        param (
            $_playerNum
        )
        
        if ($_playerNum -eq 1) {
            return $computers[0]
        }
        elseif ($_playerNum -eq 2) {
            return $computers[0] + "," + $computers[1]
        }
        elseif ($_playerNum -eq 3) {
            return $computers[0] + "," + $computers[1] + "," + $computers[2]
        }
        
        return ""
    }
    
    function SelectGame {
        param (
            $_gameName
        )
        
        if ($_gameName -eq "GameNameA") {
            return $filePathA
        }
        elseif ($_gameName -eq "GameNameB") {
            return $filePathB
        }
        elseif ($_gameName -eq "GameNameC") {
            return $filePathC
        }
        
        return ""
    }
    
    
    $username = "administrator"
    $password = "*********"
    $computers =  Get-Content -Path "C:\DStudio\GameConsole\computers.txt"
    $players = SelectPlayer $playerNum
    $filePathA = "C:\DStudio\GameConsole\GameNameA.exe"
    $filePathB = "C:\DStudio\GameConsole\GameNameB.exe"
    $filePathC = "C:\DStudio\GameConsole\GameNameC.exe"
    $filePath = SelectGame $gameName
    
    & psexec.exe \\$players -u administrator -p $password -i -d -accepteula cmd /c start $filePath
    
    // GUI program doesn't work, use psexec instead of Invoke-Command.

    Detail : PsExec v2.2

    stopGame.ps1
    $username = "administrator"
    $password = "*********"
    $computers =  Get-Content -Path "C:\DStudio\GameConsole\computers.txt"
    $processA = "GameNameA"
    $processB = "GameNameB"
    $processC = "GameNameC"
    $dirPath = "C:\PumpkinStudio\GameConsole"
    $filePath = "Video.mp4"
    
    $secureStringPwd = $password | ConvertTo-SecureString -AsPlainText -Force
    $creds = New-Object PSCredential -ArgumentList $username,$secureStringPwd
    $Session = New-PSSession -ComputerName $computers -Credential $creds
    Invoke-Command -Session $Session -ScriptBlock {Stop-Process -Name $args[0] -Force} -ArgumentList $processA
    Invoke-Command -Session $Session -ScriptBlock {Stop-Process -Name $args[0] -Force} -ArgumentList $processB
    Invoke-Command -Session $Session -ScriptBlock {Stop-Process -Name $args[0] -Force} -ArgumentList $processC
    Invoke-Command -Session $Session -ScriptBlock {Start-Process -FilePath $args[0] -WorkingDirectory $args[1]} -ArgumentList $filePath,$dirPath
    
    // Before executing Video.UI.exe (Movie & TV) through the remote command for the first time, it must be executed manually once.
    // Use Video.UI to open the video remotely. To maximize it, manually press the maximize button on the upper right

  • Freeze Program
    pyinstaller -w .\main.py
    
    // Console window invisible





    // Python Log : ./build/[ProjectName]/warn-main.txt

    Complete! But don't forget to copy form.ui to the same directory as the executable file.

    Detail : Using PyInstaller

More

這個網誌中的熱門文章

Windows10 版本1607後可啟用支援長路徑檔名 (Maximum Path Length Limitation)

標準使用者如何執行需系統管理者權限的程式

Shared Memory