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

星期二, 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月 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

全文連結