글 쓰기 앞서 이 글을 보고 따라하기 위해 필요한 파일을 먼저 첨부한다.
링크미마인은 유료 원격 프로그램으로 편안한 UI, 각종 편리한 기능도 장점이지만 무엇보다도 컴퓨터를 다시 시작하더라도 원격을 못하는 것이 아니라 윈도우 비밀번호 입력부터 시작해서 원격으로 사용이 가능하다는 점 때문에 몇년간 이용중이다.
다른 것도 써봤지만 최종적으로 링크미마인에 정착하고 주로 써왔는데 일단 전산실 사람들이 원격프로그램이란걸 좋아하지 않고 어차피 주말이나 휴일에만 급하게 쓰기 때문에 평일 일과 시간 전에만 지우고 주말에는 안지우도록 작업스케줄러를 설정했다.
전원이 꺼지거나 절전모드에 빠지면 링크미마인 작동이 되질 않아 윈도우 작업스케줄러가 작동하는 원격지 컴퓨터는 절전 모드에 빠지지 않게 전원 설정을 해놓아야 한다.
기존 링크미마인 uninstall.exe를 실행하면 제거 관련 팝업이 발생해서 손으로 일일히 제거를 해야한다. 자동화를 위해선 고쳐야했고, 그럴 필요가 없게 해달라고 회사에 요청했더니 /S 명령어를 통해 팝업같은 것 없이 스크립트 명령어 입력 시 바로 지워지는 설치 파일을 따로 받았다.

나중에 관련 프로그램 문제가 발생하여 대화하는 도중 알았는데 필자가 요청한 설치 파일 수정본은 이후 공식홈페이지에서 공식다운로드되는 설치파일로 되었다고한다. 즉 따로 받을 필요 없이 그냥 공식사이트에서 설치 파일을 받아도 가능해진 것이다..!

이제 공식홈페이지에서 정상적으로 설치해도 /S 명령어를 통해 작업스케줄러 명령어로 깔끔하게 지울 수 있다.
링크미마인 설치가 끝나면 이제 작업스케줄러로 작동을 시킬 파워쉘 스크립트를 작성해야한다. 코딩에 문외한이기에 AI의 힘을 빌렸다.
잠깐 파워쉘 스크립트를 작성하기 전에 해야할 일이 있는데, 공공데이터포털에서 주말, 공휴일에 대한 API 값을 받아오는 것이다. 그 API 값이 스크립트에 들어가면 평일이 아닌 날은 작업스케줄러가 작동하지 않도록 역할을 한다.
먼저 공공데이터포털에 접속해서 한국천문연구원_특일 정보를 검색하고 활용신청을 클릭한다.

신청을 하고 나면 마이페이지에서 관련 API key 정보가 확인가능하다. 우리는 여기서 일반 인증키 (Encoding)를 스크립트에 사용할 것이다.

API 발급은 끝났다.
이제 파워쉘에서 사용될 1개의 부모 스크립트와 1개의 자식 스크립트를 작성할 것이고 작업스케줄러에는 부모 스크립트를 등록시켜서 이후 부모 스크립트가 실행되면 자식 스크립트가 연달아 작동해서 링크미마인의 uninstall.exe가 /S 명령어를 통해 팝업 없이 실행되도록 설계할 것이다.
작성되는 2개의 스크립트는 C:\Scripts에 넣을 것이다. 만약 이 경로를 원하는 다른 경로로 변경한다면 코드 내용도 적절히 수정해야한다. 가능하면 그냥 C:\Scripts를 필자와 똑같이 세팅하자.. 안그럼 바꿀 것이 너무 많다.
스크립트 인코딩은 AI는 UTF-8로 하라고 했으나 그럴 경우 글자가 깨져서 UTF-8 with BOM으로 변경했고 이후 안정적으로 작동한다.
스크립트 내용에 로그파일 생성도 작성되어있어서 각 스크립트 실행 시 생성되고 마찬가지로 C:\Scripts에 생성된다. 만약 에러가 발생하면 로그파일 내용을 AI 줘서 해결해야하니 항상 코딩 관련 도움을 받을 때 로그 생성 및 확인이 중요하다.
먼저 부모 스크립트는 다음과 같다. 파일명은 Run_Uninstall_On_Workdays_Final.ps1 이다.
# ======================================================================================
# 최종 스크립트: Run_Uninstall_On_Workdays_Final.ps1
# 위치 : C:\Scripts\Run_Uninstall_On_Workdays_Final.ps1 (예시 경로)
# 설명 : 이 스크립트는 윈도우 작업 스케줄러를 통해 매일 실행됩니다.
# 오늘이 주말(토/일)이 아니고, 공공데이터 API를 통해 확인한 공휴일도 아닌,
# 실제 '평일'인 경우에만 지정된 프로그램 제거 스크립트를 실행합니다.
# 요구사항 : 1. data.go.kr '특일 정보 API' 일반 인증키(Encoding) 필요.
# 2. 실제로 실행할 프로그램 제거 스크립트 파일이 $TargetScriptPath 경로에 존재해야 함.
# 중요 : 이 파일은 반드시 'UTF-8' 인코딩으로 저장해야 한글 주석 및 로그가 깨지지 않습니다.
# ======================================================================================
# --- [1] 설정 (Configuration) ---
# 실행 대상 스크립트: 평일에 실행할 실제 프로그램 제거 스크립트의 전체 경로
$TargetScriptPath = "C:\Scripts\Uninstall-LinkMeMineAgent.ps1"
# 로그 파일 설정: 이 래퍼(Wrapper) 스크립트의 실행 기록을 남길 파일 경로
$LogFile = "C:\Scripts\final_workday_run_log.log"
$LogFolderPath = Split-Path -Path $LogFile -Parent # 로그 파일 경로에서 폴더 경로 자동 추출
# !!! 중요 설정: 공공데이터포털 API 인증키 !!!
# data.go.kr 에서 발급받은 '특일 정보 API'의 '일반 인증키(Encoding)' 값을 아래 따옴표 안에 붙여넣으세요.
$ApiKey = "uNzVCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # <-- 여기에 실제 API 키 (Encoding) 를 붙여넣으세요!
# --- [2] 함수: 로그 기록 (Write-FinalLog) ---
# 스크립트 실행 과정을 화면(콘솔)과 로그 파일에 기록하는 함수
function Write-FinalLog {
param(
# 로그 파일에 기록할 메시지 내용
[Parameter(Mandatory=$true)]
[string]$Message
)
# 현재 시간을 'yyyy-MM-dd HH:mm:ss' 형태로 가져옴
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# 로그 메시지 형식: "시간 - 메시지 내용"
$logEntry = "$timestamp - $Message"
# 화면(콘솔)에 로그 메시지 출력
Write-Host $logEntry
# 로그 파일을 저장할 폴더가 존재하는지 확인하고, 없으면 생성
try {
if (-not (Test-Path -Path $LogFolderPath -PathType Container)) {
# 폴더가 없으면 새로 생성 (오류 발생 시 중단)
New-Item -Path $LogFolderPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
Write-Host "$timestamp - Info: Log folder created at '$LogFolderPath'." # 폴더 생성 시 안내 메시지
}
# 로그 파일에 메시지 추가 (UTF-8 인코딩 사용, 기존 내용 뒤에 덧붙임)
$logEntry | Out-File -FilePath $LogFile -Encoding utf8 -Append -Force
} catch {
# 로그 기록 중 오류 발생 시 화면에 에러 메시지 출력
Write-Host "!!! ERROR: Failed to write log to '$LogFile'. Reason: $($_.Exception.Message)"
}
} # --- Write-FinalLog 함수 끝 ---
# --- [3] 함수: 공휴일 확인 API 호출 (Check-IsHolidayViaApi) ---
# 입력받은 날짜가 공휴일인지 data.go.kr API를 통해 확인하는 함수
function Check-IsHolidayViaApi {
param(
# API 호출에 사용할 인증키 (Encoding)
[Parameter(Mandatory=$true)]
[string]$ApiKeyParam,
# 공휴일 여부를 확인할 날짜 (datetime 객체)
[Parameter(Mandatory=$true)]
[datetime]$DateToCheck
)
# 날짜에서 년(yyyy)과 월(MM) 추출
$year = $DateToCheck.ToString('yyyy')
$month = $DateToCheck.ToString('MM')
# API 호출을 위한 URL 생성 (HTTPS 사용, ServiceKey 파라미터 이름 주의)
$apiUrl = "https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?ServiceKey=$($ApiKeyParam)&solYear=$($year)&solMonth=$($month)&_type=xml&numOfRows=50"
Write-FinalLog "Info: Attempting API Call to check holidays: $apiUrl"
# API 호출 및 응답 처리 (오류 발생 가능성 있음)
try {
# Invoke-WebRequest: 웹 요청 보내고 응답 받기 (타임아웃 30초, 기본 파싱 사용)
$responseRaw = Invoke-WebRequest -Uri $apiUrl -Method Get -TimeoutSec 30 -UseBasicParsing
# HTTP 응답 코드 확인 (200 OK 가 아니면 오류)
if ($responseRaw.StatusCode -ne 200) {
Write-FinalLog "[ERROR] API HTTP request failed. Status Code: $($responseRaw.StatusCode)"
# HTTP 오류 시 공휴일로 간주하고 함수 종료 (안전 조치)
return $true
}
# 응답받은 XML 내용을 PowerShell 객체로 변환
[xml]$responseXml = $responseRaw.Content
# === API 응답 XML 구조 유효성 검사 ===
# 기본적인 XML 구조 (response > header, response > body) 가 맞는지 확인
if ($null -eq $responseXml -or $null -eq $responseXml.response -or $null -eq $responseXml.response.header -or $null -eq $responseXml.response.body) {
Write-FinalLog "[ERROR] Invalid XML structure received from API response."
# 구조 오류 시 공휴일로 간주하고 함수 종료 (안전 조치)
return $true
}
# API 자체 결과 코드 확인 ('00'이 정상이 아니면 오류)
if ($responseXml.response.header.resultCode -ne '00') {
Write-FinalLog "[ERROR] API returned an error code. Code: $($responseXml.response.header.resultCode), Message: $($responseXml.response.header.resultMsg)"
# API 오류 코드 시 공휴일로 간주하고 함수 종료 (안전 조치)
return $true
}
# 공휴일 목록(items)이 있는지 확인 (없을 수 있음 - 해당 월에 공휴일이 없는 경우)
if ($null -eq $responseXml.response.body.items) {
Write-FinalLog "Info: No holiday 'items' found in API response for this month. Assuming workday."
# 공휴일 목록 자체가 없으면 평일로 간주하고 함수 종료
return $false
}
# 실제 공휴일 데이터(item)가 있는지 확인
$items = $responseXml.response.body.items.item
if ($null -eq $items) {
Write-FinalLog "Info: No specific 'item' data within 'items' in API response. Assuming workday."
# item 데이터가 없으면 평일로 간주하고 함수 종료
return $false
}
# === 유효성 검사 끝 ===
Write-FinalLog "Info: API Response Received and Basic Validation Passed."
# 오늘 날짜를 API 응답 형식(yyyyMMdd)에 맞게 준비
$todayApiFormat = $DateToCheck.ToString('yyyyMMdd')
# 기본적으로는 공휴일이 아니라고 가정
$isHoliday = $false
# API 응답의 공휴일 목록($items)을 확인하여 오늘 날짜와 일치하는지 검사
# $items가 여러 개(배열)일 경우와 한 개(단일 객체)일 경우를 나누어 처리
if ($items -is [array]) {
# 여러 공휴일 정보가 있는 경우, 하나씩 반복 확인
foreach ($item in $items) {
# 각 공휴일 정보(item)가 유효하고, 날짜(locdate)가 오늘과 같고, 휴일 여부(isHoliday)가 'Y' 이면
if ($null -ne $item -and $item.locdate -eq $todayApiFormat -and $item.isHoliday -eq 'Y') {
Write-FinalLog "API Result: Match found! Today ($($DateToCheck.ToString('yyyy-MM-dd'))) is a holiday ('$($item.dateName)')."
$isHoliday = $true # 공휴일임!
break # 찾았으므로 더 이상 반복할 필요 없음
}
}
} else { # 공휴일 정보가 하나만 있는 경우
# 해당 공휴일 정보가 유효하고, 날짜(locdate)가 오늘과 같고, 휴일 여부(isHoliday)가 'Y' 이면
if ($null -ne $items -and $items.locdate -eq $todayApiFormat -and $items.isHoliday -eq 'Y') {
Write-FinalLog "API Result: Match found! Today ($($DateToCheck.ToString('yyyy-MM-dd'))) is a holiday ('$($items.dateName)')."
$isHoliday = $true # 공휴일임!
}
}
# 최종 확인 결과: $isHoliday 변수에 따라 오늘이 공휴일인지 여부 반환 (true 또는 false)
if (-not $isHoliday) { Write-FinalLog "API Result: No matching holiday found for today in the API data." }
return $isHoliday
} catch {
# API 호출 또는 응답 처리 중 예상치 못한 오류 발생 시
Write-FinalLog "[CRITICAL ERROR] Exception occurred during API call or processing: $($_.Exception.Message)"
Write-FinalLog "Warning: Treating today as a holiday due to a critical error during API check."
# 치명적 오류 발생 시 안전하게 공휴일로 간주하고 함수 종료
return $true
}
} # --- Check-IsHolidayViaApi 함수 끝 ---
# ======================================================================================
# --- [4] 메인 스크립트 실행 로직 ---
# ======================================================================================
Write-FinalLog "===== Final Workday Check Script Start ====="
$overallExitCode = 0 # 스크립트 최종 종료 상태 코드 (0: 성공, 0 외: 오류)
# --- 4.1: 필수 설정 확인 (API 키) ---
# API 키가 설정되지 않았거나 기본값("YOUR_API_KEY_HERE")이면 스크립트 중단
if ($ApiKey -eq "YOUR_API_KEY_HERE" -or -not $ApiKey -or $ApiKey.Length -lt 30) { # API 키 길이 최소 30자 가정
Write-FinalLog "[FATAL ERROR] API Key is not configured correctly in the script. Please edit the script and add your key. Exiting."
$overallExitCode = 1 # 오류 상태로 설정
Write-FinalLog "===== Final Workday Check Script End (With Error) ====="
exit $overallExitCode # 오류 코드로 종료
}
# --- 4.2: 오늘 날짜 및 요일 확인 ---
$today = Get-Date
$dayOfWeek = $today.DayOfWeek
Write-FinalLog "Info: Today is $($today.ToString('yyyy-MM-dd')), Day of week is $dayOfWeek."
# --- 4.3: 주말(토/일) 여부 확인 ---
if ($dayOfWeek -eq [DayOfWeek]::Saturday -or $dayOfWeek -eq [DayOfWeek]::Sunday) {
# 주말인 경우, 아무 작업도 하지 않고 스크립트 종료
Write-FinalLog "Result: Today is Weekend. Target script will not run."
} else {
# 주말이 아닌 경우 (평일 또는 공휴일 가능성 있음)
Write-FinalLog "Info: Today is a Weekday. Checking API for holiday status..."
# --- 4.4: 공휴일 여부 확인 (API 호출) ---
$isTodayHoliday = $false # 기본값: 평일
try {
# 위에서 정의한 Check-IsHolidayViaApi 함수 호출
$isTodayHoliday = Check-IsHolidayViaApi -ApiKeyParam $ApiKey -DateToCheck $today
} catch {
# Check-IsHolidayViaApi 함수 호출 자체에서 오류 발생 시 (드문 경우)
Write-FinalLog "[FATAL ERROR] A critical error occurred when trying to call the API check function: $($_.Exception.Message)"
$isTodayHoliday = $true # 함수 호출 실패 시 안전하게 공휴일로 간주
}
# --- 4.5: 최종 실행 결정 및 대상 스크립트 실행 ---
# API 확인 결과, 공휴일이 아닌 경우 (즉, 진짜 '평일'인 경우)
if (-not $isTodayHoliday) {
Write-FinalLog "Result: Today is determined to be a Workday (Not a weekend and not a holiday based on API)."
Write-FinalLog "Action: Attempting to run the target script: '$TargetScriptPath'"
# 실제 제거 스크립트 파일($TargetScriptPath)이 존재하는지 확인
if (Test-Path -Path $TargetScriptPath -PathType Leaf) {
try {
# 대상 스크립트를 별도의 PowerShell 프로세스로 실행 (관리자 권한은 작업 스케줄러에서 부여받음)
# 실행 정책(-ExecutionPolicy Bypass)을 적용하여 실행
$commandToRun = "powershell.exe -ExecutionPolicy Bypass -File ""$TargetScriptPath"""
Write-FinalLog "Info: Executing command -> $commandToRun"
# --- !!! 여기가 수정된 핵심 부분입니다 !!! ---
# Start-Process 사용하여 대상 스크립트 실행하고 완료될 때까지 대기
$process = Start-Process -FilePath "powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -File ""$TargetScriptPath""" -Wait -PassThru -ErrorAction Stop
# 대상 스크립트의 실제 종료 코드 확인
$targetExitCode = $process.ExitCode
Write-FinalLog "Info: Target script execution completed. Exit Code returned: $targetExitCode."
Write-FinalLog "Info: Check the target script's own log (e.g., uninstall_log.log) for execution details."
# --- !!! 수정된 핵심 부분 끝 !!! ---
# 대상 스크립트가 오류 코드(0이 아닌 값)를 반환하면 경고 로그 남김
if ($targetExitCode -ne 0) {
Write-FinalLog "[Warning] The target script ('$TargetScriptPath') may have finished with an error (Exit Code: $targetExitCode)."
# 필요 시, 대상 스크립트의 오류 코드를 이 래퍼 스크립트의 최종 종료 코드로 반영 가능
$overallExitCode = $targetExitCode
}
} catch {
# 대상 스크립트 실행 중 오류 발생 시
Write-FinalLog "[ERROR] An error occurred while trying to execute the target script ('$TargetScriptPath'): $($_.Exception.Message)"
$overallExitCode = 1 # 오류 상태로 설정
}
} else {
# 대상 스크립트 파일을 찾을 수 없는 경우
Write-FinalLog "[ERROR] The target script file was not found at the specified path: '$TargetScriptPath'"
$overallExitCode = 1 # 오류 상태로 설정
}
} else {
# API 확인 결과 공휴일이거나, API 확인 중 오류가 발생한 경우
Write-FinalLog "Result: Today is determined to be a Holiday (or an API error occurred). Target script will not run."
}
} # 주말이 아닌 경우의 로직 끝 (if-else)
# --- [5] 스크립트 종료 ---
Write-FinalLog "===== Final Workday Check Script End ====="
exit $overallExitCode # 최종 상태 코드로 스크립트 종료 (0: 성공, 0 외: 오류)
# --- 스크립트 전체 끝 ---
이 코드 그대로 복붙해서 본인 컴퓨터에 복사 붙여넣기해서 사용하면 된다.
다만 API key만 공공데이터포털에서 발급 받은 본인의 고유 encoding key를 넣어줘야한다. 아래 내용을 위에서 찾아서 바꿔준다.
====================================================================
# !!! 중요 설정: 공공데이터포털 API 인증키 !!!
# data.go.kr 에서 발급받은 ‘특일 정보 API’의 ‘일반 인증키(Encoding)’ 값을 아래 따옴표 안에 붙여넣으세요.
$ApiKey = “uNzVCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX” # <– 여기에 실제 API 키 (Encoding) 를 붙여넣으세요!
====================================================================
-> 이 항목에서 본인의 API key를 넣어준다.
다른건 고칠 게 없다.
두번째로 자식 스크립트인 Uninstall-LinkMeMineAgent.ps1 이다.
# ==============================================================================
# 스크립트: Uninstall-LinkMeMineAgent.ps1
# 위치 : C:\Scripts\Uninstall-LinkMeMineAgent.ps1 (예시 경로)
# 설명 : 'LinkMeMine Agent'의 uninstall.exe를 /S 옵션으로 실행하여 자동 제거하고 로그를 기록합니다.
# 프로그램 제거 여부를 확인하고, 스크립트의 최종 종료 코드를 반환합니다.
# ==============================================================================
# --- 설정 ---
$programDisplayNameForSearch = "LinkMeMine Agent" # 레지스트리 검색 시 사용할 이름 (부분 일치 가능)
$logFolderPath = "C:\Scripts" # 로그 파일 경로
$logFileName = "uninstall_log.log" # 로그 파일 이름
$logFilePath = Join-Path -Path $logFolderPath -ChildPath $logFileName
# 제거 프로그램 정보
$uninstallerPath = "C:\Program Files (x86)\LinkMeMine\Service\Agent\uninstall.exe"
$silentArgs = "/S" # 자동 제거 인수
# --- 함수: 로그 기록 ---
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp - $Message"
Write-Host $logEntry # 콘솔 출력
# 로그 폴더 생성 (존재하지 않을 경우)
if (-not (Test-Path -Path $logFolderPath -PathType Container)) {
try {
New-Item -Path $logFolderPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
Write-Host "$timestamp - 로그 폴더 '$logFolderPath'를 생성했습니다." # 폴더 생성 시 직접 콘솔 출력
} catch {
Write-Error "$timestamp - 로그 폴더 '$logFolderPath' 생성 중 오류 발생: $($_.Exception.Message)"
# 로그 폴더 생성 실패 시 스크립트 진행 의미 없으므로 종료 (오류 코드 반환)
exit 2 # 로그 폴더 생성 오류는 2번 코드로 가정
}
}
# 로그 파일에 기록
try {
$logEntry | Out-File -FilePath $logFilePath -Encoding utf8 -Append -Force
} catch {
Write-Error "$timestamp - 로그 파일 '$logFilePath'에 쓰기 중 오류 발생: $($_.Exception.Message)"
# 로그 파일 쓰기 실패해도 스크립트 핵심 로직은 진행하되, 콘솔에는 오류 표시
}
}
# --- 스크립트 본문 ---
Write-Log "스크립트 시작: '$programDisplayNameForSearch' 프로그램 자동 제거 시도 (uninstall.exe $silentArgs 실행)..."
$scriptExitCode = 0 # 스크립트 최종 종료 코드 (0: 성공, 0 외: 오류)
Write-Log "제거 프로그램 경로: $uninstallerPath"
Write-Log "사용할 자동 제거 인수: $silentArgs"
# 1. 제거 대상 프로그램(uninstall.exe) 파일 존재 여부 확인
if (Test-Path -Path $uninstallerPath -PathType Leaf) {
Write-Log "제거 프로그램($uninstallerPath)을 찾았습니다. 자동 제거를 시작합니다..."
try {
Write-Log "명령어 실행 시도: $uninstallerPath $silentArgs"
# Start-Process 를 사용하여 제거 프로그램 실행하고 완료될 때까지 대기
# -PassThru 로 프로세스 객체를 받아 종료 코드 확인
# -Verb RunAs는 작업 스케줄러에서 "가장 높은 권한으로 실행" 시 필요 없을 수 있음.
# 만약 UAC 프롬프트 문제가 발생하면 -Verb RunAs 제거 고려.
$uninstallProcess = Start-Process -FilePath $uninstallerPath -ArgumentList $silentArgs -Wait -PassThru #-Verb RunAs
$uninstallerExitCode = $uninstallProcess.ExitCode
Write-Log "제거 프로그램 실행 완료. uninstall.exe 종료 코드: $uninstallerExitCode"
# uninstall.exe가 0이 아닌 코드를 반환했는지 확인 (선택적: 일부 제거 프로그램은 성공해도 0이 아닐 수 있음)
if ($uninstallerExitCode -ne 0) {
Write-Log "[주의] 제거 프로그램(uninstall.exe)이 0이 아닌 종료 코드($uninstallerExitCode)를 반환했습니다. 이는 오류일 수도, 정상일 수도 있습니다."
# 이 경우, 실제 제거 여부 확인이 더 중요해짐
}
# 2. 실제 프로그램 제거 여부 확인
# 방법 1: uninstaller.exe 파일 자체가 사라졌는지 확인 (uninstaller가 자신도 지우는 경우)
# 방법 2: 레지스트리에서 프로그램 설치 정보 확인 (더 일반적)
Start-Sleep -Seconds 3 # 제거 프로세스가 파일 정리 등을 할 시간을 약간 줌
$isStillInstalled = $false # 기본적으로 제거되었다고 가정
if (Test-Path -Path $uninstallerPath -PathType Leaf) {
Write-Log "uninstall.exe 파일이 여전히 존재합니다. 레지스트리를 통해 설치 여부를 확인합니다."
# 레지스트리 확인
$uninstallRegPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
$installedProgramEntry = Get-ItemProperty -Path $uninstallRegPaths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "*$programDisplayNameForSearch*" }
if ($installedProgramEntry) {
$isStillInstalled = $true
Write-Log "[경고] 레지스트리에서 '$($installedProgramEntry.DisplayName)' 프로그램이 여전히 확인됩니다. 제거에 실패했을 수 있습니다."
} else {
Write-Log "레지스트리에서 '$programDisplayNameForSearch' 관련 항목을 찾을 수 없습니다. 프로그램이 제거된 것으로 보입니다."
}
} else {
Write-Log "uninstall.exe 파일이 사라졌습니다. 프로그램이 제거된 것으로 강하게 추정됩니다."
}
if ($isStillInstalled) {
Write-Log "[결과] '$programDisplayNameForSearch' 프로그램 제거에 실패했거나 부분적으로만 제거된 것 같습니다."
$scriptExitCode = 1 # 제거 실패 시 오류 코드 1
} else {
Write-Log "[결과] '$programDisplayNameForSearch' 프로그램이 성공적으로 제거된 것으로 판단됩니다."
}
} catch {
Write-Log "[오류] '$programDisplayNameForSearch' 프로그램 자동 제거 중 예외 발생: $($_.Exception.Message)"
# Write-Log "오류 상세: $($_.Exception | Format-List | Out-String)" # 필요한 경우 주석 해제
$scriptExitCode = 1 # 예외 발생 시 오류 코드 1
}
} else {
# uninstall.exe 파일이 처음부터 없는 경우
Write-Log "제거 프로그램 '$uninstallerPath'를 찾을 수 없습니다."
# 이 경우, 프로그램이 이미 제거되었는지 레지스트리로 한번 더 확인
$uninstallRegPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
$installedProgramEntry = Get-ItemProperty -Path $uninstallRegPaths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "*$programDisplayNameForSearch*" }
if ($installedProgramEntry) {
Write-Log " -> 하지만 레지스트리에는 '$($installedProgramEntry.DisplayName)' 프로그램이 여전히 존재합니다. uninstall.exe 누락 오류입니다."
$scriptExitCode = 1 # 제거 파일은 없는데 프로그램은 설치된 경우 오류
} else {
Write-Log " -> 레지스트리에서도 '$programDisplayNameForSearch' 관련 항목을 찾을 수 없습니다. 이미 제거된 상태로 보입니다."
$scriptExitCode = 0 # 이미 제거된 상태면 성공으로 간주
}
}
Write-Log "스크립트 실행 완료. 최종 종료 코드: $scriptExitCode"
exit $scriptExitCode # 스크립트의 최종 종료 코드 반환
# --- 스크립트 끝 ---
이건 이대로 복사붙여넣기하면되고 특별히 고칠 것은 없다.
가능하면 code-server같은 프로그램으로 코딩 복붙을 하는게 좋지만 정 안되면 메모장에다가 하고 txt를 ps1 형식으로 바꿔줘도 가능하다.
이제 윈도우 작업스케줄러에 등록을 해주자. 오히려 이 단계에서 에러가 있었는데 로그파일이 발생하는 것도 아니라 제대로된 원인을 몰라 고치는데 시간이 많이 걸렸다.
다음과 같이 쭉 따라오면 된다.
먼저, 윈도우 작업스케줄러를 실행하고 우측의 기본 작업 만들기를 클릭한다.

이름을 적어주고 설명도 원하면 적어준다.

다음으로 작업 트리거에서 매일을 선택한다.

매일 실행할 시간을 선택해준다. 일과시간 전으로 선택했다.

작업은 프로그램 시작을 그대로 유지한다.

여기서가 중요하다.
프로그램/스크립트에는 powershell.exe 라고 입력한다.
인수 추가(옵션)에 -ExecutionPolicy Bypass -File “C:\Scripts\Run_Uninstall_On_Workdays_Final.ps1” 라고 입력한다.

마칠 때 속성 대화 상자 열기 체크 박스를 클릭해서 이후 설정을 이어나간다.

마치게 되면 자동으로 속성 창이 뜬다. 로그온 여부 상관 없이 실행하고 반드시 가장 높은 권한으로 실행하는 것을 클릭하자. 안그러면 제대로 실행이 안된다. 구성 대상은 본인 윈도우 버전에 맞게 해준다. window10으로 해주자.

트리거 설정한대로 두고 넘어가자.

동작도 그대로 넘어가자.

조건에서 이 작업을 실행하기 위해 절전 모드 종료를 클릭해주자.

가장 중요한 설정란이다.
혹시 원격지 컴퓨터가 설정한 시간보다 늦게 켜지는 경우에도 작업스케줄러가 동작할 수 있게 “예약된 시작 시간을 놓친 경우 ~” 옵션을 클릭해준다.
“다음 시간 이상 작업이 실행되면 중지”에서 1시간으로 해준다. 실제 작업시간은 5초 이내이니 1시간이면 충분하다.
가장 중요한게 “작업이 이미 실행 중이면 다음 규칙 적용”에서 기존 인스턴트 중지를 체크해준다. 혹시라도 무슨 버그같은 현상으로 작업이 계속 실행중 상태가 되는 경우 다음날 7시반이 되었을 때 기존 이상한 작업을 중지하고 새로운 작업이 시작될 수 있게 한다.
필자는 처음에 작업스케줄러를 설정하면 잘되고 이후 며칠 지나면 안됐는데 기존 인스턴스 중지 설정을 하고 해결됐다.

기록탭 아무것도 안한다.

이제 확인 버튼을 누르고 모든 설정을 끝낸 후 테스트를 해본다. 생성된 작업을 우클릭 해서 실행을 클릭해서 팝업 같은 것 없이 프로그램 삭제가 되면 완료다.
평소에는 “준비”상태로 있으면 되고 실행 시간이 됐을 때 “실행중” 상태로 바뀌고 작업이 1시간 지나면 종료하도록 설정했기에 1시간이 지나면 다시 “준비”상태로 바뀐다.

테스트 실행 후 C:\Scripts에 로그파일이 생성된다.
아래 내용과 같이 있으면 성공이다. 로그가 생성되다가 만다던가 다른 에러가 있다면 AI에게 문의 후 해결할 수 있다.
<final_workday_run_log>
2025-05-13 09:04:12 - ===== Final Workday Check Script Start =====
2025-05-13 09:04:12 - Info: Today is 2025-05-13, Day of week is Tuesday.
2025-05-13 09:04:12 - Info: Today is a Weekday. Checking API for holiday status...
2025-05-13 09:04:12 - Info: Attempting API Call to check holidays: https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?ServiceKey=본인의APIkey
2025-05-13 09:04:12 - Info: API Response Received and Basic Validation Passed.
2025-05-13 09:04:12 - API Result: No matching holiday found for today in the API data.
2025-05-13 09:04:12 - Result: Today is determined to be a Workday (Not a weekend and not a holiday based on API).
2025-05-13 09:04:12 - Action: Attempting to run the target script: 'C:\Scripts\Uninstall-LinkMeMineAgent.ps1'
2025-05-13 09:04:12 - Info: Executing command -> powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\Uninstall-LinkMeMineAgent.ps1"
2025-05-13 09:04:29 - Info: Target script command sent. Exit Code returned: 0.
2025-05-13 09:04:29 - Info: Check the target script's own log (e.g., uninstall_log.log) for execution details.
2025-05-13 09:04:29 - ===== Final Workday Check Script End =====
<uninstall_log>
2025-05-13 09:04:12 - 스크립트 시작: 'LinkMeMine Agent' 프로그램 자동 제거 시도 (uninstall.exe /S 실행)...
2025-05-13 09:04:13 - 제거 프로그램 경로: C:\Program Files (x86)\LinkMeMine\Service\Agent\uninstall.exe
2025-05-13 09:04:13 - 사용할 자동 제거 인수: /S
2025-05-13 09:04:13 - 제거 프로그램(C:\Program Files (x86)\LinkMeMine\Service\Agent\uninstall.exe)을 찾았습니다. 자동 제거를 시작합니다...
2025-05-13 09:04:13 - 명령어 실행 시도: C:\Program Files (x86)\LinkMeMine\Service\Agent\uninstall.exe /S
2025-05-13 09:04:29 - 'LinkMeMine Agent' 프로그램이 성공적으로 자동 제거된 것으로 확인되었습니다.
2025-05-13 09:04:29 - 스크립트 실행 완료.
본문에서 언급한 필요한 링크미마인 설치파일, 스크립트는 본문 처음 첨부파일에 다 있으니 참고해서 진행하면 된다.
이제 컴퓨터가 켜져 있기만 하면 원격프로그램을 주말, 공휴일에 사용할 수 있다. 필자는 보통 금요일 일과시간 전 또는 긴 연휴 전날에 링크미마인 설치를 하고 퇴근해서 필요 시 원격으로 쓰고 있다.