ClickOnce를 통한 .NET Desktop Application + Embed Python 모듈 배포

안녕하세요. .NET Framework 4.8 WPF Application에서 python 스크립트를 호출하는 것을 고려하고 있습니다.

지금까지는 대상 PC에 Python3를 설치하고, 스크립트를 실행하기 위해서 필요한 python 모듈들을 선행하여 직접 설치 한 뒤, 프로그램에서 python 스크립트를 호출하고 있었습니다. (System.Diagnostics.Process를 통한 호출)

하지만 이제 조금 더 나아가서 대상 PC에 python을 사용자가 직접 설치하지 않고 고려하는 방법을 찾고 있습니다. 현재 찾아본 방법은,

  1. 대상 PC는 Windows 10 PC이므로 windows powershell 을 이용하여 python3를 설치하고 필요로하는 module을 연계하여 설치하는 방법.

예를 들어 이런 것입니다.

# Python이 설치되어 있는지 확인
$pythonInstalled = $null -ne (Get-Command python -ErrorAction SilentlyContinue)

if (-not $pythonInstalled) {
    Write-Output "Python이 설치되어 있지 않습니다. 설치를 시작합니다..."
    
    # 프로세서 아키텍처 확인
    $processorArchitecture = (Get-WmiObject -Class Win32_Processor).Architecture

    # Python 설치 프로그램 URL 설정
    switch ($processorArchitecture) {
        0 { # x86
            $installerUrl = "https://www.python.org/ftp/python/3.11.8/python-3.11.8.exe"
            break
        }
        9 { # x64 (AMD64)
            $installerUrl = "https://www.python.org/ftp/python/3.11.8/python-3.11.8-amd64.exe"
            break
        }
        12 { # ARM64
            $installerUrl = "https://www.python.org/ftp/python/3.11.8/python-3.11.8-arm64.exe"
            break
        }
        default {
            Write-Output "지원되지 않는 아키텍처입니다."
            exit
        }
    }

    # 다운로드할 위치와 파일 이름
    $installerPath = "$env:TEMP\python-installer.exe"
    
    # 설치 프로그램 다운로드
    Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
    
    # Python 설치 (여기서는 기본 옵션으로 설치)
    Start-Process -FilePath $installerPath -Args "/quiet InstallAllUsers=1 PrependPath=1" -Wait
    
    # 설치 프로그램 삭제
    Remove-Item -Path $installerPath

    Write-Output "Python 설치가 완료되었습니다."
} else {
    Write-Output "Python이 이미 설치되어 있습니다."
}

# 필요한 Python 패키지 설치
$packages = @("six", "Pillow", "jinja2==3.0.3", "airtest", "markupsafe") # 필요한 패키지를 여기에 추가

foreach ($package in $packages) {
    $packageName = $package.Split('==')[0]
    $packageVersion = $package.Split('==')[1]
    $packageInfo = python -m pip show $packageName
    
    if (-not $packageInfo) {
        Write-Output "$packageName 설치를 시도합니다..."
        Start-Process -FilePath "python" -Args "-m pip install $package" -Wait
        Write-Output "$packageName 설치가 완료되었습니다."
    } else {
        Write-Output "$packageName 이미 설치되어 있습니다."
    }
}

Write-Output "모든 필요한 패키지 설치가 완료되었습니다."

위 파워쉘 코드를 실행하면 내 Windows PC에 특정 python3 버전이 설치되어 있는지 검사한 뒤 패키지를 설치합니다.

이후 .netfx wpf app을 통해 python 스크립트를 돌리는 것입니다.

  1. PyInstaller 이용.

python 스크립트는 스크립트 자체로 배포되는 만큼, 배포된 PC에서 스크립트 수정을 할 수 있기 때문에 스크립트 언어 자체가 가지는 보안적 취약점이 있으며, 마우스를 통해 쉽게 실행하지 못하고 터미널을 통해 실행해야 합니다.
그래서 이 PyInstaller 를 이용하면 python module 스크립트를 .exe 파일로 변형하여 내부에 python 설치기능까지 들어있는 채로 실행파일 형태로 배포할 수 있어서 1번의 방법보다 더 쉽게 설치가 가능합니다.

  1. Python.Included / Pythonnet 이용

이게 제가 궁금한 방법인데요.
.NET 에서 python 함께 폴리글랏하여 사용하는 방법에는 크게 IronPython과 pythonnet 이 있다고 할 수 있는데, IronPython은 Python을 주력언어로 사용하여 Python으로 Windows Forms를 한다던지 하는 용도로 사용할 수 있고, Python.NET은 python이 .NET 위에서 실행되어 .NET 어셈블리를 우리가 사용하듯 python 모듈을 .NET의 기능처럼 사용할 수 있게 하는 개념으로 이해를 하고 있습니다.
따라서 python .net의 가장 큰 장점은 C# 으로 새로 작성할 필요없이 이미 가지고 있는 python 스크립트를 python.net을 이용하여 그냥 실행하면 되는 것으로 이해하고 있습니다.
여기에 곁들여서 Python.Included 까지 이용하면 내가 배포하는 .NET Application에 Python을 내장하여 배포할 수 있는 것으로 이해했습니다.
즉 이 3번 방법을 이용하면 .NET 위에서 python이 실행되므로, 지원되는 pip 패키지와 버전만 맞다면 OS에 Python3를 설치하지 않아도 되는 것으로 이해했습니다.

따라서 1->2->3번의 순서대로 가장 Best한 방법이 될 것 같습니다.
1번은 이미 해놨지만, 3번이 가장 궁금합니다.
혹시 Python.Included를 이용해서 .NET Desktop Application을 배포해보신 분이 계실까요?

Q. 사용할 때 pip install을 통해 받을 수 있는 모듈과 내가 정의한 모듈 둘 다 사용할 수 있게 하는 코드는 어떤 형태일까요? 예제를 공유해주시면 감사드리겠습니다. 현재 저는 ClickOnce를 통해 배포하고 있습니다.

1개의 좋아요

도움안되는 방법 하나 알려드리자면
py파일을 그대로 소스파일 형태로 보내진 않을테니, 파이선 소스를 exe로 추출하여 같이 포함시켜서 배포하는겁니다.

1개의 좋아요

Python.NET은 CPython(python.exe)를 C#으로 Interop하는 라이브러리고

Python.Included는 파이썬을 자동으로 설치해주는 라이브러리입니다.
(패키지 버전과 동일한 파이썬을 설치합니다.)
즉, 최초 실행시 임시 폴더에 파이썬 설치와 패키지 설치하는 것으로 시작됩니다.

Add-Type -Path ".\Python.Included.dll" -ReferencedAssemblies ".\Python.Runtime.dll"
[Python.Included.Installer]::InstallDirectory = [System.IO.Path]::GetFullPath("Python")
[Python.Included.Installer]::SetupPython().Wait()

Python.Included를 사용하시고
최초 설치 시 위와 같이 Powershell 코드를 사용하여
파이썬과 PIP를 설치하시면 될 것 같습니다.

Installer.InstallDirectory = Path.GetFullPath("Python");
await Installer.SetupPython();

물론 C#에서도 동일하게 경로 지정해주셔야 되겠지요.

2개의 좋아요

파이썬은 py 파일로 그대로 배포가 됩니다. 말씀하신 대로 하는 것이 2번에 소개한 PyInstaller를 사용하는 것입니다.

1개의 좋아요

파이썬을 설치한다고 보는 게 맞군요.
소스를 실행해봤는데 환경변수까지 설정하는 일반적인 설치는 아니라서 그런지 터미널에서 python -v 를 하면 동작하지 않습니다.
프로그램을 켤때마다 임시폴더에 설치가 되고 이후에 임시폴더를 참조해서 설치 없이 계속 쓰다가 임시폴더에서 지워지면 또 설치하고…그런식인가보군요.

제가 궁금했던 것은 pythonnet 을 이용하면 기존의 파이썬 파일을 수정없이 사용할 수 있는지 였습니다.

1개의 좋아요
  1. 명령 프롬프트를 통해 사용하시려면
    Python.Include를 사용하면, 별도 경로에 설치되므로. 해당 경로로 이동하여 Python.exe를 사용하면 됩니다.
  2. C#코드를 통해 파이썬 명령을 실행하시려면
    Py.Import를 통해서 py 파일을 실행하시면 됩니다.
// py 파일의 경로를 Path에 추가.
dynamic sys = Py.Import("sys");
sys.path.append(Environment.CurrentDirectory);

// py 파일 import 후 함수 실행
Py.Import("test").InvokeMethod("Run"); //InvokeMethod 또는 dynamic 사용

test.py를 실행하는 C# 코드

def Run():
    # ... 실행할 파이썬 코드 추가

test.py Python 코드

1개의 좋아요