Use 8632 when 631 is taken, install standalone Windows and macOS builds beside the driver instead of over it, open the LAN firewall ports, and give silent NSIS dialogs a default plus an install log. Co-authored-by: George Lambert <marchon@gmail.com>
230 lines
6.4 KiB
Go
230 lines
6.4 KiB
Go
//go:build windows
|
|
|
|
package netprint
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
const runKey = `HKLM\Software\Microsoft\Windows\CurrentVersion\Run`
|
|
const ownerKey = `HKLM\Software\Verae\NetPrint`
|
|
|
|
type osControl struct{}
|
|
|
|
func (osControl) Start() error { return sc("start", winService) }
|
|
func (osControl) Stop() error {
|
|
return delayed("cmd.exe", "/c", "ping -n 2 127.0.0.1 >NUL & sc stop "+winService)
|
|
}
|
|
func (osControl) Restart() error {
|
|
return delayed("cmd.exe", "/c", "sc stop "+winService+" & ping -n 2 127.0.0.1 >NUL & sc start "+winService)
|
|
}
|
|
func (osControl) LaunchAtLogin() (bool, error) {
|
|
out, err := exec.Command("sc.exe", "qc", winService).CombinedOutput()
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return strings.Contains(strings.ToUpper(string(out)), "AUTO_START"), nil
|
|
}
|
|
func (osControl) SetLaunchAtLogin(on bool) error {
|
|
mode := "demand"
|
|
if on {
|
|
mode = "auto"
|
|
}
|
|
return sc("config", winService, "start=", mode)
|
|
}
|
|
func (osControl) TrayAtLogin() (bool, error) {
|
|
out, err := exec.Command("reg.exe", "query", runKey, "/v", "VeraeNetPrintTray").CombinedOutput()
|
|
return err == nil && strings.Contains(string(out), "VeraeNetPrintTray"), nil
|
|
}
|
|
func (osControl) SetTrayAtLogin(on bool) error {
|
|
exe, _ := Executable()
|
|
return setTrayFor(exe, on)
|
|
}
|
|
|
|
func sc(args ...string) error {
|
|
out, err := exec.Command("sc.exe", args...).CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("sc %s: %w (%s)", strings.Join(args, " "), err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func delayed(args ...string) error {
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x00000008} // DETACHED_PROCESS
|
|
return cmd.Start()
|
|
}
|
|
|
|
func trayNextTo(serviceExe string) string {
|
|
serviceExe = strings.Trim(serviceExe, `"`)
|
|
if serviceExe == "" {
|
|
return `verae-print.exe tray`
|
|
}
|
|
sibling := filepath.Join(filepath.Dir(serviceExe), "verae-netprint-tray.exe")
|
|
if _, err := os.Stat(sibling); err == nil {
|
|
return `"` + sibling + `"`
|
|
}
|
|
return `"` + serviceExe + `" tray`
|
|
}
|
|
|
|
func setTrayFor(serviceExe string, on bool) error {
|
|
if !on {
|
|
return exec.Command("reg.exe", "delete", runKey, "/v", "VeraeNetPrintTray", "/f").Run()
|
|
}
|
|
return exec.Command("reg.exe", "add", runKey, "/v", "VeraeNetPrintTray", "/t", "REG_SZ", "/d", trayNextTo(serviceExe), "/f").Run()
|
|
}
|
|
|
|
func serviceBinPath(exe string) string {
|
|
return fmt.Sprintf(`"%s" netprint`, exe)
|
|
}
|
|
|
|
func currentServiceExe() string {
|
|
out, err := exec.Command("sc.exe", "qc", winService).CombinedOutput()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return parseWindowsBinPath(string(out))
|
|
}
|
|
|
|
func peerBinary(owner string) string {
|
|
rel := filepath.Join("Verae", "NetPrint", "bin", "verae-print.exe")
|
|
if owner == "standalone" {
|
|
rel = filepath.Join("Verae", "PrintStamp", "bin", "verae-print.exe")
|
|
}
|
|
var roots []string
|
|
if p := os.Getenv("ProgramW6432"); p != "" {
|
|
roots = append(roots, p)
|
|
}
|
|
if p := os.Getenv("ProgramFiles"); p != "" {
|
|
roots = append(roots, p)
|
|
}
|
|
roots = append(roots, `C:\Program Files`)
|
|
seen := map[string]bool{}
|
|
for _, root := range roots {
|
|
if seen[root] {
|
|
continue
|
|
}
|
|
seen[root] = true
|
|
p := filepath.Join(root, rel)
|
|
if _, err := os.Stat(p); err == nil {
|
|
return p
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func ensureFirewall() {
|
|
removeFirewall()
|
|
rules := []struct{ name, proto, port string }{
|
|
{"Verae NetPrint IPP", "TCP", "631"},
|
|
{"Verae NetPrint IPP Fallback", "TCP", "8632"},
|
|
{"Verae NetPrint mDNS", "UDP", "5353"},
|
|
}
|
|
for _, r := range rules {
|
|
out, err := exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
|
|
"name="+r.name, "dir=in", "action=allow", "protocol="+r.proto,
|
|
"localport="+r.port, "profile=private,domain").CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("netprint: firewall %s: %v (%s)", r.name, err, strings.TrimSpace(string(out)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func removeFirewall() {
|
|
for _, name := range []string{"Verae NetPrint IPP", "Verae NetPrint IPP Fallback", "Verae NetPrint mDNS"} {
|
|
_ = exec.Command("netsh", "advfirewall", "firewall", "delete", "rule", "name="+name).Run()
|
|
}
|
|
}
|
|
|
|
func setOwnerReg(name string, on bool) {
|
|
v := "0"
|
|
if on {
|
|
v = "1"
|
|
}
|
|
_ = exec.Command("reg.exe", "add", ownerKey, "/v", name, "/t", "REG_DWORD", "/d", v, "/f").Run()
|
|
}
|
|
|
|
func ownerReg(name string) bool {
|
|
out, err := exec.Command("reg.exe", "query", ownerKey, "/v", name).CombinedOutput()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return strings.Contains(string(out), "0x1")
|
|
}
|
|
|
|
// Install registers the VeraeNetPrint service and the tray Run key.
|
|
func Install(owner, tokenFile string) error {
|
|
if _, err := addOwner(owner); err != nil {
|
|
return err
|
|
}
|
|
if owner == "driver" {
|
|
setOwnerReg("Driver", true)
|
|
} else {
|
|
setOwnerReg("Standalone", true)
|
|
}
|
|
exe, err := Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfgPath := SystemConfigPath()
|
|
_ = os.MkdirAll(filepath.Dir(cfgPath), 0o755)
|
|
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
|
|
if err := os.WriteFile(cfgPath, ExampleJSON(defaultDataDir()), 0o644); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
current := currentServiceExe()
|
|
chosen := selectServiceBinary(exe, current)
|
|
bin := serviceBinPath(chosen)
|
|
if current == "" {
|
|
if err := sc("create", winService, "binPath=", bin, "start=", "auto", "DisplayName=", "Verae Network Printer"); err != nil {
|
|
return err
|
|
}
|
|
} else if chosen != current {
|
|
_ = exec.Command("sc.exe", "stop", winService).Run()
|
|
if err := sc("config", winService, "binPath=", bin); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_ = exec.Command("sc.exe", "description", winService, "Verae network timestamp printer").Run()
|
|
_ = exec.Command("sc.exe", "config", winService, "start=", "auto").Run()
|
|
copyToken(tokenFile)
|
|
ensureFirewall()
|
|
if err := sc("start", winService); err != nil {
|
|
return err
|
|
}
|
|
return setTrayFor(chosen, true)
|
|
}
|
|
|
|
// Uninstall removes the service only when neither installer still owns it.
|
|
func Uninstall(owner string) error {
|
|
o, err := removeOwner(owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if owner == "driver" {
|
|
setOwnerReg("Driver", false)
|
|
} else {
|
|
setOwnerReg("Standalone", false)
|
|
}
|
|
if !o.empty() || ownerReg("Driver") || ownerReg("Standalone") {
|
|
if other := peerBinary(owner); other != "" {
|
|
_ = exec.Command("sc.exe", "stop", winService).Run()
|
|
_ = sc("config", winService, "binPath=", serviceBinPath(other))
|
|
_ = sc("start", winService)
|
|
_ = setTrayFor(other, true)
|
|
}
|
|
return nil
|
|
}
|
|
_ = exec.Command("sc.exe", "stop", winService).Run()
|
|
_ = exec.Command("sc.exe", "delete", winService).Run()
|
|
_ = setTrayFor("", false)
|
|
removeFirewall()
|
|
return nil
|
|
}
|