Power Platform/Power Automate-데스크톱
처음 Power Automate 사용자 지정 작업 만들기
nanenchanga
2023. 7. 3. 09:00
728x90
Power Automate Custom Action - 윈도우 영역 캡처
Power Automate Desktop 에서 사용자가 직접 작성한 코드로 데스크탑 흐름을 제작할 수 있도록 공개되었다. .NetFramework로만 제작이 가능하다(이후 Core이상도 지원을 기대한다. - .NetFrameWork만 지원으로 타 OS 실행을 막을 가능성도 있다) 정식문서에서 자세한 설명을 볼 수 있다.
요구사항
- .NetFrameWork 4.7.2 이상
- Visual Studio 2022 이상의 IDE
- Power Automate for Desktop 버전 2.32 이상
- Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK
- 프리미엄 라이선스
Visual Studio용 템플릿 설치 후 프로젝트 작성
- Visual Studio 패키지 관리자에서 다음 면령을 실행하여 템플릿을 설치(설치시 한글이 깨져나온다.)
dotnet new -i Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.Templates
- 처음 만드는 것이면 Power Automate Sample Module을 선택
- 프로젝트 이름과 경로를 지정(경로에 한글이 없는것을 추천)
- 흐름 내에서 사용할 단계의 모듈 ID를 입력하고 'Create Test Project'는 해제하자(2023.06.30 기준으로는 해제하는 것이 좋다. 이후 달라질 수 있다), .resx file 사용은 리소스로 설명등을 관리할 것이면 체크 아니면 소스코드에 직접 작성하면 된다.
- 우선 프로젝트 파일에서
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>
- cs파일을 연다.
- 주요한 특성(Attributes)을 살펴보면
Action : 단계를 정의한다
Throws : 에러시 이름
InputArgument : 단계의 입력값 정의
OutputArgument : 단계의 출력값 정의 - 현재 만드려는건 윈도우에서 영역을 캡처하는 것을 만드려 한다. 우선 입력 특성을 포함하는 특성들을 추가한다. 다음 코드를 기존 프로퍼티를 삭제하고 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; }
- 이미지를 캡처하는 작업을 하는 클래스를 생성한다. 소스코드는 다음과 같다 같은 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);
}
}
}
}
- 최종적인 소스코드는 다음과 같다.
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);
}
}
}
}
- 이후 빌드 후 생성된 net472 폴더안에 dll 이 생성된 것을 확인한다.
인증서 작성과 설치
커스텀 액션은 cab파일로 만들어야한다. 그전에 DLL파일을 조직에서 신뢰하는 인증서로 서명되하고 설치해야한다.(서명은 각 사용하는 장치마다 설치해야한다.)
- Power Shell 을 실행한다.
- 다음 코드를 복사하여 붙여넣고 실행한다. 이때 파일위치와 암호는 적당히 변경한다.
$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
- 위 명령어로 생성된 서명파일을 실행한다.
- 인증서 생성시 입력한 비밀번호를 입력후 아래 이미지에 해당되는 항목들을 체크한다.
- '모든 인증서를 다음 저장소에 저장'을 선택 후 '인증서 저장소'를 '신뢰할 수 있는 루트 인증 기관'으로 선택
- 인증서 가져오기 마법사를 완료한다.
- 경고가 표시되면 '예'
DLL 파일서명
- Visual Studio의 개발자 명령 프롬프트에서 다음 명령을 실행하여 DLL에 서명, 경로를 적절하게 변경한다.(한글이 있다면 제대로 안될 수 있다. 한글이 없는 경로로 옮겨서 실행한다.)
Signtool sign /f "{서명위치}\cert.pfx" /p "{비밀번호}" /fd SHA256 "{dll 생성 파일경로}\Modules.Module1.dll"
cab 파일로 패키징
- 생성된 DLL 파일은 종속 파일과 함께 CAB 파일로 패키지되어야 한다.
- 아래 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
- 이후 PowerShell 스크립트에 dll경로, cab 파일이 생성될 경로, cab 파일이름을 입력하여 실행
{powershell 스크립트 위치}makeCabFromDirectory.ps1 "{dll경로}" "{cab파일생성위치}" "{cab파일이름}.cab"
cab파일서명
- 패키지된 cab 파일과 dll 파일도 서명해야 한다.
Signtool sign /f "{서명경로}\cert.pfx" /p "{비밀번호}" /fd SHA256 "{cab파일경로}.cab"
사용자 지정 작업 업로드
- Power Automate 웹으로 로그인한다.
- '데이터' -> '사용자 지정 작업' -> '+ 사용자 지정 작업 업로드' 클릭
- 사용자 지정 작업의 이름을 입력하고 cab파일을 선택하여 업로드한다.
- 작업목록에 추가된것을 확인한다.
사용자 지정 작업을 사용하는 방법
- Power Automate for Desktop을 연다. 흐름을 새로 생성하거나 수정을 선택하여 디자이너를 연다.
- 좌측 작업 탭에서 '추가 작업 표시'를 클릭
- 자산 라이브러리에서 추가한 사용자 지정 작업을 선택히여 추가한다.
- 이후 흐름내부에 작업을 추가해서 사용할 수 있게된다.(사용하는 데스크탑에 서명이 제대로 설치되어있다면 공유가 가능하다)
- 생성한 사용자 지정 작업을 실행해 본다.
- 이후 경로에 파일이 제대로 생성되었다면 성공이다.
## 해당 예제 GitHub 주소
PowerPlatforms/PowerAutomate Custom Actions at main · nanenchanga53/PowerPlatforms (github.com)
728x90
반응형