ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 처음 Power Automate 사용자 지정 작업 만들기
    Power Platform/Power Automate-데스크톱 2023. 7. 3. 09:00
    728x90

    Power Automate Custom Action - 윈도우 영역 캡처

    Power Automate Desktop 에서 사용자가 직접 작성한 코드로 데스크탑 흐름을 제작할 수 있도록 공개되었다. .NetFramework로만 제작이 가능하다(이후 Core이상도 지원을 기대한다. - .NetFrameWork만 지원으로 타 OS 실행을 막을 가능성도 있다) 정식문서에서 자세한 설명을 볼 수 있다.

    요구사항

    Visual Studio용 템플릿 설치 후 프로젝트 작성

    1. Visual Studio 패키지 관리자에서 다음 면령을 실행하여 템플릿을 설치(설치시 한글이 깨져나온다.)
    dotnet new -i Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.Templates

    스크린샷 2023-06-29 160755

    1. 처음 만드는 것이면 Power Automate Sample Module을 선택

    스크린샷 2023-06-29 162443

    1. 프로젝트 이름과 경로를 지정(경로에 한글이 없는것을 추천)

    스크린샷 2023-06-29 163530

    1. 흐름 내에서 사용할 단계의 모듈 ID를 입력하고 'Create Test Project'는 해제하자(2023.06.30 기준으로는 해제하는 것이 좋다. 이후 달라질 수 있다), .resx file 사용은 리소스로 설명등을 관리할 것이면 체크 아니면 소스코드에 직접 작성하면 된다.

    스크린샷 2023-06-29 164309

    1. 우선 프로젝트 파일에서 Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK 버전을 지정해줘야한다.(현제 지정하지 않으면 어떤 버전을 사용해야하는지 알지 못한다) 버전은 1.4.232.23122-rc 지정한다.(기존 페키지레퍼런스 설정은 삭제하고 다음으로 대체)
    <ItemGroup>
        <PackageReference Include="Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK" Version="1.4.232.23122-rc" />
    </ItemGroup>
    1. cs파일을 연다.
    2. 주요한 특성(Attributes)을 살펴보면
      Action : 단계를 정의한다
      Throws : 에러시 이름
      InputArgument : 단계의 입력값 정의
      OutputArgument : 단계의 출력값 정의
    3. 현재 만드려는건 윈도우에서 영역을 캡처하는 것을 만드려 한다. 우선 입력 특성을 포함하는 특성들을 추가한다. 다음 코드를 기존 프로퍼티를 삭제하고 Excute 함수 위에 추가한다. 캡처를 시작하는 위치의 x,y 값 그리고 가로세로 길이, 저장하는 디렉토리 위치를 지정한다.
    
    /// <summary>
    /// 캡처 좌상단 위치 X값
    /// </summary>
    [InputArgument(Order = 1)]
    public int LeftX { get; set; }
    
    /// <summary>
    /// 캡처 좌상단 위치 Y값
    /// </summary>
    [InputArgument(Order = 2)]
    public int LeftY { get; set; }
    
    /// <summary>
    /// 캡처 가로크기
    /// </summary>
    [InputArgument(Order = 3)]
    public int Width{ get; set; }
    
    /// <summary>
    /// 캡처 세로크기
    /// </summary>
    [InputArgument(Order = 4)]
    public int Hight { get; set; }
    
    /// <summary>
    /// 저장위치
    /// </summary>
    [InputArgument(Order = 5)]
    public string Dir { get; set; }
    
    1. 이미지를 캡처하는 작업을 하는 클래스를 생성한다. 소스코드는 다음과 같다 같은 namespace안에 넣자(따로 파일을 관리하고 싶으면 분리) 이때 최상단에 using System.Drawing, using System.Drawing.Imaging을 추가한다.
    /// <summary>
    /// 이미지 저장 클래스
    /// </summary>
    public class ImgCapture
    {
        /// <summary>
        /// 이미지 저장을 위한 시작위치 X
        /// </summary>
        private int StartX { get; set; } = 0;
    
        /// <summary>
        /// 이미지 저장을 위한 시작위치 Y
        /// </summary>
        private int StartY { get; set; } = 0;
    
        /// <summary>
        /// 이미지 저장을 위한 길이
        /// </summary>
        private int ImageWidth { get; set; } = 0;
    
        /// <summary>
        /// 이미지 저장을 위한 높이
        /// </summary>
        private int ImageHight { get; set; } = 0;
    
        private string filePath = null;
    
        /// <summary>
        /// Core 이상을 사용할 수 있게되면 튜플로 변경하자
        /// </summary>
        /// <param name="startX">시작위치X</param>
        /// <param name="startY">시작위치Y</param>
        /// <param name="imgWidth">이미지 너비</param>
        /// <param name="imgHight">이미지 높이</param>
        public ImgCapture(int startX = 0, int startY = 0, int imageWidth = 0, int imageHight = 0)
        {
            StartX = startX;
            StartY = startY;
            ImageWidth = ImageWidth;
            ImageHight = ImageHight;
        }
    
        /// <summary>
        /// 저장위치
        /// </summary>
        /// <param name="path">저장위치경로(파일 이름,형식까지)</param>
        public void SetPath(string path)
        {
            filePath = path;
        }
    
        /// <summary>
        /// 
        /// </summary>
        public void CaptureWindow()
        {
            if (filePath != null)
            {
                if (StartX == 0 || StartY == 0)
                    return;
    
                using (Bitmap bitmap = new Bitmap((int)ImageWidth, (int)ImageHight))
                {
                    using (Graphics g = Graphics.FromImage(bitmap))
                    {
                        g.CopyFromScreen(StartX, StartY, 0, 0, bitmap.Size);
                    }
    
                    bitmap.Save(filePath, ImageFormat.Png);
                }
            }
        }
    }
    
    
    1. 최종적인 소스코드는 다음과 같다.
    using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.Attributes;
    using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Drawing.Imaging;
    using System.Drawing;
    
    namespace Modules.Capture
    {
    
        /// <summary>
        /// 이미지 저장 클래스
        /// </summary>
        public class ImgCapture
        {
            /// <summary>
            /// 이미지 저장을 위한 시작위치 X
            /// </summary>
            private int StartX { get; set; } = 0;
    
            /// <summary>
            /// 이미지 저장을 위한 시작위치 Y
            /// </summary>
            private int StartY { get; set; } = 0;
    
            /// <summary>
            /// 이미지 저장을 위한 길이
            /// </summary>
            private int ImageWidth { get; set; } = 0;
    
            /// <summary>
            /// 이미지 저장을 위한 높이
            /// </summary>
            private int ImageHight { get; set; } = 0;
    
            private string filePath = null;
    
            /// <summary>
            /// Core 이상을 사용할 수 있게되면 튜플로 변경하자
            /// </summary>
            /// <param name="startX">시작위치X</param>
            /// <param name="startY">시작위치Y</param>
            /// <param name="imgWidth">이미지 너비</param>
            /// <param name="imgHight">이미지 높이</param>
            public ImgCapture(int startX = 0, int startY = 0, int imageWidth = 0, int imageHight = 0)
            {
                StartX = startX;
                StartY = startY;
                ImageWidth = ImageWidth;
                ImageHight = ImageHight;
            }
    
            /// <summary>
            /// 저장위치
            /// </summary>
            /// <param name="path">저장위치경로(파일 이름,형식까지)</param>
            public void SetPath(string path)
            {
                filePath = path;
            }
    
            /// <summary>
            /// 
            /// </summary>
            public void CaptureWindow()
            {
                if (filePath != null)
                {
                    if (StartX == 0 || StartY == 0)
                        return;
    
                    using (Bitmap bitmap = new Bitmap((int)ImageWidth, (int)ImageHight))
                    {
                        using (Graphics g = Graphics.FromImage(bitmap))
                        {
                            g.CopyFromScreen(StartX, StartY, 0, 0, bitmap.Size);
                        }
    
                        bitmap.Save(filePath, ImageFormat.Png);
                    }
                }
            }
        }
    
    
        [Action(Id = "CaptrueCustom", Order = 1)]
        [Throws("CaptureError")] // TODO: change error name (or delete if not needed)
        public class CaptureCustomAction : ActionBase
        {
            /// <summary>
            /// 캡처 좌상단 위치 X값
            /// </summary>
            [InputArgument(Order = 1)]
            public int LeftX { get; set; }
    
            /// <summary>
            /// 캡처 좌상단 위치 Y값
            /// </summary>
            [InputArgument(Order = 2)]
            public int LeftY { get; set; }
    
            /// <summary>
            /// 캡처 가로크기
            /// </summary>
            [InputArgument(Order = 3)]
            public int Width{ get; set; }
    
            /// <summary>
            /// 캡처 세로크기
            /// </summary>
            [InputArgument(Order = 4)]
            public int Hight { get; set; }
    
            /// <summary>
            /// 저장위치
            /// </summary>
            [InputArgument(Order = 5)]
            public string Dir { get; set; }
    
            public override void Execute(ActionContext context)
            {
    
                try
                {
                    ImgCapture imgCapture = new ImgCapture(LeftX, LeftY, Width, Hight);
    
                    imgCapture.SetPath(Dir);
    
                    imgCapture.CaptureWindow();
    
                }
                catch (Exception e)
                {
                    if (e is ActionException) throw;
    
                    throw new ActionException("ActionError", e.Message, e.InnerException);
                }
    
            }
        }
    }
    
    1. 이후 빌드 후 생성된 net472 폴더안에 dll 이 생성된 것을 확인한다.

    스크린샷 2023-06-30 154801

    인증서 작성과 설치

    커스텀 액션은 cab파일로 만들어야한다. 그전에 DLL파일을 조직에서 신뢰하는 인증서로 서명되하고 설치해야한다.(서명은 각 사용하는 장치마다 설치해야한다.)

    1. Power Shell 을 실행한다.
    2. 다음 코드를 복사하여 붙여넣고 실행한다. 이때 파일위치와 암호는 적당히 변경한다.
    $certname = "Self-Signed Certificate"
    $cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -Type CodeSigningCert  -Subject "CN=$certname" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256 -NotAfter (Get-Date).AddYears(30)
    $mypwd = ConvertTo-SecureString -String "{비밀번호}" -Force -AsPlainText
    Export-PfxCertificate -Cert $cert -FilePath "{파일위치변경}\cert.pfx" -Password $mypwd
    1. 위 명령어로 생성된 서명파일을 실행한다.

    스크린샷 2023-06-30 162544스크린샷 2023-06-30 163456

    1. 인증서 생성시 입력한 비밀번호를 입력후 아래 이미지에 해당되는 항목들을 체크한다.

    스크린샷 2023-06-30 163526

    1. '모든 인증서를 다음 저장소에 저장'을 선택 후 '인증서 저장소'를 '신뢰할 수 있는 루트 인증 기관'으로 선택

    스크린샷 2023-06-30 163622

    1. 인증서 가져오기 마법사를 완료한다.

    스크린샷 2023-06-30 163707

    1. 경고가 표시되면 '예'

    DLL 파일서명

    1. Visual Studio의 개발자 명령 프롬프트에서 다음 명령을 실행하여 DLL에 서명, 경로를 적절하게 변경한다.(한글이 있다면 제대로 안될 수 있다. 한글이 없는 경로로 옮겨서 실행한다.)
    Signtool sign /f "{서명위치}\cert.pfx" /p "{비밀번호}" /fd SHA256 "{dll 생성 파일경로}\Modules.Module1.dll"

    cab 파일로 패키징

    1. 생성된 DLL 파일은 종속 파일과 함께 CAB 파일로 패키지되어야 한다.
    2. 아래 PowerShell 스크립트(makeCabFromDirectory.ps1)를 만들어 저장한다.
    param(
    
        [ValidateScript({Test-Path $_ -PathType Container})]
        [string]
        $sourceDir,
    
        [ValidateScript({Test-Path $_ -PathType Container})]
        [string]
        $cabOutputDir,
    
        [string]
        $cabFilename
    )
    
    $ddf = ".OPTION EXPLICIT
    .Set CabinetName1=$cabFilename
    .Set DiskDirectory1=$cabOutputDir
    .Set CompressionType=LZX
    .Set Cabinet=on
    .Set Compress=on
    .Set CabinetFileCountThreshold=0
    .Set FolderFileCountThreshold=0
    .Set FolderSizeThreshold=0
    .Set MaxCabinetSize=0
    .Set MaxDiskFileCount=0
    .Set MaxDiskSize=0
    "
    $ddfpath = ($env:TEMP + "\customModule.ddf")
    $sourceDirLength = $sourceDir.Length;
    $ddf += (Get-ChildItem $sourceDir -Filter "*.dll" | Where-Object { (!$_.PSIsContainer) -and ($_.Name -ne "Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.dll") } | Select-Object -ExpandProperty FullName | ForEach-Object { '"' + $_ + '" "' + ($_.Substring($sourceDirLength)) + '"' }) -join "`r`n"
    $ddf | Out-File -Encoding UTF8 $ddfpath
    makecab.exe /F $ddfpath
    Remove-Item $ddfpath
    1. 이후 PowerShell 스크립트에 dll경로, cab 파일이 생성될 경로, cab 파일이름을 입력하여 실행
    {powershell 스크립트 위치}makeCabFromDirectory.ps1 "{dll경로}" "{cab파일생성위치}" "{cab파일이름}.cab"

    cab파일서명

    1. 패키지된 cab 파일과 dll 파일도 서명해야 한다.
    Signtool sign /f "{서명경로}\cert.pfx" /p "{비밀번호}" /fd SHA256 "{cab파일경로}.cab"

    사용자 지정 작업 업로드

    1. Power Automate 웹으로 로그인한다.
    2. '데이터' -> '사용자 지정 작업' -> '+ 사용자 지정 작업 업로드' 클릭

    스크린샷 2023-07-03 083324

    1. 사용자 지정 작업의 이름을 입력하고 cab파일을 선택하여 업로드한다.

    스크린샷 2023-07-03 083551

    1. 작업목록에 추가된것을 확인한다.

    스크린샷 2023-07-03 083757

    사용자 지정 작업을 사용하는 방법

    1. Power Automate for Desktop을 연다. 흐름을 새로 생성하거나 수정을 선택하여 디자이너를 연다.
    2. 좌측 작업 탭에서 '추가 작업 표시'를 클릭

    스크린샷 2023-07-03 084024

    1. 자산 라이브러리에서 추가한 사용자 지정 작업을 선택히여 추가한다.

    스크린샷 2023-07-03 084224

    1. 이후 흐름내부에 작업을 추가해서 사용할 수 있게된다.(사용하는 데스크탑에 서명이 제대로 설치되어있다면 공유가 가능하다)

    스크린샷 2023-07-03 084358

    1. 생성한 사용자 지정 작업을 실행해 본다.

    스크린샷 2023-07-03 084543

    1. 이후 경로에 파일이 제대로 생성되었다면 성공이다.

     

    ## 해당 예제 GitHub 주소

    PowerPlatforms/PowerAutomate Custom Actions at main · nanenchanga53/PowerPlatforms (github.com)

    728x90
    반응형

    댓글

Designed by Tistory.