顯示具有 Autohotkey 標籤的文章。 顯示所有文章
顯示具有 Autohotkey 標籤的文章。 顯示所有文章

星期四, 11月 27, 2025

Autohotkey 自動監測啟動 VirtualBox VM

用 GPT 產生的腳本,蠻好用的 XD。作用是在每次 Windows 開機時,自動監測某個 VirtualBox VM 是否已經自動運行。如果沒有運行,就自動啟動它。

需求背景是研究計畫中請了一位學弟妹幫忙,而標註程式需要用到 Windows,結果學弟家裡只能使用 Mac 囧,只好自己在 VirtualBox 上架設一個 VM。如果不這樣做,線上的 VPS 提供者所給的套餐都很貴資源又少。用 1GB 或 2GB 的 RAM 搭一個 shared vCPU core 跑 Windows,性能實在不敢想像。

自己電腦建 VM,然後把 RAM 和 Disk 切多一些,使用FRP Tunnel出去,體驗實在好太多。唯一問題就是 ADSL 有時不穩定,這點還要設法剋服。

; --- settings ---
VBoxManage := "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
VMName := "win10"
CheckIntervalMs := 1000 ; how often to check when running
BackoffAfterStartMs := 5000 ; wait a bit after starting to avoid rapid retries
; -----------------

#NoEnv
#SingleInstance, Force
SetBatchLines, -1

Loop
{
	; Build: "C:...\VBoxManage.exe" list runningvms | findstr /I /C:""Win10""
	Cmd := """"  VBoxManage  """" "list runningvms | findstr /I /C:" """""" VMName  """"""
	RunWait, % ComSpec " /C " Cmd, , Hide
	if (ErrorLevel != 0) {
	; Not found -> start headless
		Run, % """" VBoxManage """ startvm """ VMName """ --type headless", , Hide
		Sleep, %BackoffAfterStartMs%
	} else 
	{
		Sleep, %CheckIntervalMs%
	}
}
全文連結

星期二, 10月 24, 2023

土炮 Windows 10 ISO

因為平常會使用至少兩台桌電和一台筆電,最近又可能組新電腦,就來研究怎麼自製 Win10 ISO,避免每次新安裝一台電腦都要重灌一堆軟體才能做映像檔 。

個人步驟如下

找 ISO 檔

http://www.uup.ee/ 提供的檔案可以整合更新檔 

使用 NTLite

去除 Windows Defender (我用其他免費防毒)、Cortana,其中組件要移除,後方對應設定要改。並調整 Windows 10 更新為自動下載但不自動安裝。禁止自動升級驅動程式(!),禁止在使用流量計費網路的時候下載更新。

虛擬機建立系統

用 VirtualBox 建 VM,用 NTLite 精簡好乾淨 ISO 裝系統,系統要建兩個硬碟(另一個槽後面要拿來暫存自建的 install.wim),預裝一卡車軟體,中間當然重要步驟都要快照,安裝軟體包括:

Locale Emulator
.Net framework 3.5、4.8
Directx 9.0C
Lumicons
Mozilla Firefox
Google chrome
Mactype
Notepad++
ConEmu
各版本 RPG Maker runtime
新酷音輸入法
Macrium Reflect Free
BandiZip
VC runtime 網路上 all-in-one

系統微調

a. 裝好後用以下指令讓 Notepad++ 完全取代 Notepad

reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v "Debugger" /t REG_SZ /d "\"%ProgramFiles%\Notepad++\notepad++.exe\" -notepadStyleCmdline -z" /f

b. Win+R --> shell:startup--> 建立 .ahk 檔案,輸入以下內容,讓 Ctrl-space 可以代替 Win-Space,微軟注音移除。

^Space::#Space

未來安裝英文語言包後,這些變更可以達成 Ctrl-Space 切換新酷音,不會被 Windows 10 的 Shift 和輸入法搞的很不習慣

c. 這次 Stopupdates10 暫時沒裝,先試試有沒有其他方法確保能裝安全性更新,同時避免被硬塞 Windows 11。上次用了 Stopupdates10 雖然擋了更新,但是還原不回來 QQ

d. 關掉系統還原

e. 照這篇用 gpedit 去除最近使用過的檔案紀錄

f. 用這篇gpedit 方法限制 Windows 最多更新到 20H2(主要是不想被硬塞 Windows 11) 

系統一般化

sysprepd 還原到 OOBE、完事後關機。中間遇到衝突的軟體,參考這篇內容,用 Powershell remove-appxpackage 指令去除。

Dism 打包

我用 Macrium Reflect Free 建立的救援媒體,下

dism /Capture-Image /ImageFile:D:\install.wim /CaptureDir=E: /Name:MyWin10Image /Compress:fast

此處注意,dism 後面參數不需要 /boot。

建立土炮 Windows 安裝資料夾

dism 打包的 install.wim 用共享資料夾傳到 host PC。把先前 NTLite 精簡過的 ISO 解壓縮到暫時目錄,裡面的 source/install.wim 用自製 install.wim 蓋掉。

建立可開機 ISO

用 imgburn 將上個步驟的暫時目錄建立為 bootable ISO,其中「選項」我選擇包含隱藏、系統檔,檔案系統我選 UDF,「進階」->「可開機光碟」一定要勾選讓映像檔可開機,下方開機映像檔指向映像檔目錄 boot\etfsboot.com,載入碟區應為 8(我用 64bit ISO),其實 imgburn 偵測到燒錄內容有 install.wim,會自動偵測並警告參數錯誤,一律照 imgburn 的建議做修改! 

測試

VirtualBox 開另外一台 VM,用新建的 ISO 檔安裝系統,確認系統能進入桌面,額外裝的一大堆程式都在,完成!

全文連結

星期四, 8月 10, 2023

Python-docx 試用

處理報告又遇到麻煩。某個篩檢業務,除了完成電腦報告之外,每份報告還要手寫給病人看的通知書,增加很多工作量。

還好遇到殊勝的 python-docx套件,可以直接對Word檔進行search還有replaceStack overflow 上也有可以進行修改使用的範例。確認可行後,找了同仁,要了通知書原始電腦範本docxtemplate。弄個前端 HTML form + AHK + 從其他表單帶數字,command line 丟到 Python對模板寫資料,通知書產生器就完成了。我減少工作量,病人不用忍受難看的手寫字。這週處理 paper work減少工作量就感覺到成就感 XD

全文連結

星期四, 8月 11, 2022

Autohotkey 抓多螢幕解析度方法

在公司打報告,希望用 OCR 判讀螢幕數字並自動抄錄以減少作業時間。這功能需要每個螢幕的長寬大小(pixel)才能讓程式知道 OCR 該看螢幕截圖的哪個位置。

原本是用 Autohotkey 的 SysGet Monitor 解決。但 SysGet 傳回的資料是錯的,也沒體力除錯了。為了省工不想寫額外程式,希望能用簡單的腳本解決。後來發現 powershell 可以做這件事情 

Get-WmiObject -Class Win32_VideoController | Out-File screen.out

輸出結果導向到檔案儲存,然後回 Autohotkey 從檔案中用 regex 從 VideoModeDescription 讀出解析度數值,然後做些加減乘除就有各螢幕大小和座標了。 值得一提的是其他的失敗方案:

Add-Type -AssemblyName System.Windows.Forms
System.Windows.Forms.Screen]::AllScreens

 這個 powershell 方法能得到全螢幕解析度,但會受到 scaling factor 干擾,得到的 width、height 必須乘以該螢幕的對應 scaling factor 才能還原為真實的螢幕解析度,但找了一陣子沒發現快速得到 scaling factor 的方法,因此就放棄此法。

[System.Windows.Forms.SystemInformation]::PrimaryMonitorSize 

這個方法則是只能得到主螢幕解析度,看不到第二第三顆的。

 年紀真的大了,花了一個半小時研究城市又開始眩暈 QQ

--------------------------------------------

$pinvokeCode = @" 
using System; 
using System.Runtime.InteropServices; 
using System.Collections.Generic;
namespace Resolution 
{ 
    [StructLayout(LayoutKind.Sequential)] 
    public struct DEVMODE1 
    { 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmDeviceName; 
        public short dmSpecVersion; 
        public short dmDriverVersion; 
        public short dmSize; 
        public short dmDriverExtra; 
        public int dmFields; 
        public short dmOrientation; 
        public short dmPaperSize; 
        public short dmPaperLength; 
        public short dmPaperWidth; 
        public short dmScale; 
        public short dmCopies; 
        public short dmDefaultSource; 
        public short dmPrintQuality; 
        public short dmColor; 
        public short dmDuplex; 
        public short dmYResolution; 
        public short dmTTOption; 
        public short dmCollate; 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmFormName; 
        public short dmLogPixels; 
        public short dmBitsPerPel; 
        public int dmPelsWidth; 
        public int dmPelsHeight; 
        public int dmDisplayFlags; 
        public int dmDisplayFrequency; 
        public int dmICMMethod; 
        public int dmICMIntent; 
        public int dmMediaType; 
        public int dmDitherType; 
        public int dmReserved1; 
        public int dmReserved2; 
        public int dmPanningWidth; 
        public int dmPanningHeight; 
    }; 
	
	[Flags()]
	public enum DisplayDeviceStateFlags : int
	{
		/// <summary>The device is part of the desktop.</summary>
		AttachedToDesktop = 0x1,
		MultiDriver = 0x2,
		/// <summary>The device is part of the desktop.</summary>
		PrimaryDevice = 0x4,
		/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
		MirroringDriver = 0x8,
		/// <summary>The device is VGA compatible.</summary>
		VGACompatible = 0x10,
		/// <summary>The device is removable; it cannot be the primary display.</summary>
		Removable = 0x20,
		/// <summary>The device has more display modes than its output devices support.</summary>
		ModesPruned = 0x8000000,
		Remote = 0x4000000,
		Disconnect = 0x2000000
	}
	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
	public struct DISPLAY_DEVICE 
	{
		  [MarshalAs(UnmanagedType.U4)]
		  public int cb;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
		  public string DeviceName;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceString;
		  [MarshalAs(UnmanagedType.U4)]
		  public DisplayDeviceStateFlags StateFlags;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceID;
		[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceKey;
	}
    class User_32 
    { 
        [DllImport("user32.dll")] 
        public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode); 
        [DllImport("user32.dll")] 
        public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags); 
		[DllImport("user32.dll")]
		public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
        public const int ENUM_CURRENT_SETTINGS = -1; 
        public const int CDS_UPDATEREGISTRY = 0x01; 
        public const int CDS_TEST = 0x02; 
        public const int DISP_CHANGE_SUCCESSFUL = 0; 
        public const int DISP_CHANGE_RESTART = 1; 
        public const int DISP_CHANGE_FAILED = -1; 
    } 
    public class Displays
    {
		public static IList<string> GetDisplayNames()
		{
			var returnVals = new List<string>();
			for(var x=0U; x<1024; ++x)
			{
				DISPLAY_DEVICE outVar = new DISPLAY_DEVICE();
				outVar.cb = (short)Marshal.SizeOf(outVar);
				if(User_32.EnumDisplayDevices(null, x, ref outVar, 1U))
				{
					returnVals.Add(outVar.DeviceName);
				}
			}
			return returnVals;
		}
		
		public static string GetCurrentResolution(string deviceName)
        {
            string returnValue = null;
            DEVMODE1 dm = GetDevMode1();
            if (0 != User_32.EnumDisplaySettings(deviceName, User_32.ENUM_CURRENT_SETTINGS, ref dm))
            {
                returnValue = dm.dmPelsWidth + "," + dm.dmPelsHeight;
            }
            return returnValue;
        }
		
		public static IList<string> GetResolutions()
		{
			var displays = GetDisplayNames();
			var returnValue = new List<string>();
			foreach(var display in displays)
			{
				returnValue.Add(GetCurrentResolution(display));
			}
			return returnValue;
		}
		
        private static DEVMODE1 GetDevMode1() 
        { 
            DEVMODE1 dm = new DEVMODE1(); 
            dm.dmDeviceName = new String(new char[32]); 
            dm.dmFormName = new String(new char[32]); 
            dm.dmSize = (short)Marshal.SizeOf(dm); 
            return dm; 
        } 
    }
} 
"@
Add-Type $pinvokeCode
[Resolution.Displays]::GetResolutions();

方法出處 

結果本以為能用的方法失敗,最後換了這個方法,在 Autohotkey 中呼叫 powershell.exe 執行這段腳本,用 Out-File 導出成文字檔用 regex parse。 話說 powershell 可以直接執行 C# 代碼耶,作為「腳本語言」這會不會太強了點?XDDDDD

全文連結

星期日, 7月 24, 2022

再戰 VB6

VB6 自動化研究又有了進度。爆肝摸索、找了可靠的第三方庫、殺了一卡車 bug、解完山一樣高的例外,看完海一樣深的 log 以後,自動化表單查詢程式終於穩定運行,也不枉昨天搞到 9PM。

禮拜日開一天,就看到機器人已經查了約 500 份病歷。這樣查詢完 N=2~3K 的 study 還真的就電腦多開幾天的事。資料齊了後用 python 簡單統計和 NLP,應該就能生成大部分夥伴和統計專家需要的資料。
 
可以的話還是希望能用 SQL。從 VB6 form dump 資料其實比直接下 SQL 複雜很多,效能還差。幾行 SQL 就可以解決的事,為了合法合規不得不用複雜的方式。
 

 
全文連結

星期六, 6月 25, 2022

吐槽 VB6

 最近又一次受 VB6.0 荼毒迫害。

最近和其他單位同事合作回溯性分析,分析公司用戶資料。資料能透過資訊系統一筆筆查,問題是筆數多(N>2K),資料分散在不同報表程式和網頁表單上。因各種因素,不能直連 production DB 下 SQL。反覆請人代撈實務不可行。但政策不禁自動化軟體代替鍵盤滑鼠操作報表軟體,只要遵循審查流程。

然後就又跟 VB6.0 槓上了。很大部份資料需透過一 VB6.0 報表軟體查詢。報表軟體上有 ListView 顯示用戶的多筆紀錄,操作者點選 ListView 上的紀錄,程式跳出對應該紀錄的進一步細節。

真人鍵盤滑鼠操作沒啥問題,用自動化 VB6 就開始不講武德。

在家中做模擬,就發現 MSDN 上的東西很多對 VB6 沒用。例如,MSDN 說 LVM_SETITEMSTATE 可以改變 ListView 目前被圈選的 item。實際使用畫面上 ListView 被 highlight 的 item 會變,但後續 side effect 不會觸發。又例如 MSDN 說 LVM_SETITEM 可改變 ListView 呈現的資料,結果實際使用只會改畫面上的資料,隨便重排資料修改就不見了。

那用 code injection 解決呢?很久前就試過,VB6 編出來的 code 本質是編成 native code 的虛擬機操作,大量意義不明的中間變數和莫名操作,一切 control flow 最終回歸 MSVBVM60.DLL 和元件庫。有效操作被嚴重稀釋。逆向天然自帶虛擬機的 binary 嚴重超過我業餘用戶的能力,我連自己寫的 mock 都沒辦法分析,生產環境中的報表軟體就不用試了。

最後,又一次倒在 VB6 鐵拳之下,還是只有模擬鍵盤滑鼠按鍵的方式可行。方案沒技術含量,而且使用期間電腦不能操作,但自動點選存資料還是弄出來了,之後走審查流程後就在公司內正式開發、使用。順利的話能解決分析需求,還有機會與另外一個 side project (搜尋引擎)形成連動,做成一條龍。

業餘 side project 坑好像越挖越大,一開始是好玩,然後是實用,現在目標變成發展產業鏈了 XDD。但還是要吐槽 VB6,根本就上個時代的遺毒...

全文連結