Windows 10 시간 동기화 실패 문제…(feat Golang)

윈도우10 시간 동기화 실패

몇일동안 인터넷 검색해서 나온 해결법 다 해보고도 해결 못한 문제가 “윈도우 시간 동기화 실패” 문제입니다. 이사하기 전 KT 쓸때는 아무 문제 없었는데 이사후 지역 케이블 인터넷으로 바뀐뒤 부터 안되는걸 보니 제가 뭘해도 안될거 같아서 포기하고 윈도우 시간 동기화 하는 프로그램을 하나 만들었습니다.

func IsAdmin() bool {
    _, err := os.Open("\\.\PHYSICALDRIVE0")
    if err != nil {
        //관리자 권한이 없음
        return false
    }
    //관리자 권한이 있음.
    return true
}

윈도우에서 시간값 변경을 할려면 관리자 권한이 필요합니다. 위 코드는 단순하게 권한이 있는지 없는지 유무만 체크합니다.

func runMeElevated() {
    verb := "runas"
    exe, _ := os.Executable()
    cwd, _ := os.Getwd()
    args := strings.Join(os.Args[1:], " ")

    verbPtr, _ := syscall.UTF16PtrFromString(verb)
    exePtr, _ := syscall.UTF16PtrFromString(exe)
    cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
    argPtr, _ := syscall.UTF16PtrFromString(args)

    // var showCmd int32 = 0 //SW_HIDE
    var showCmd int32 = 5 //SW_SHOW

    err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
    if err != nil {
        panic(err)
    }
}

이 코드는 현재 실행파일을 관리자 권한으로 다시 실행 하는 코드입니다.

if IsAdmin() {
    //관리자 권한으로 실행시킬 코드들
} else {
    //관리자가 아니면 관리자 권한으로 다시 실행함
    runMeElevated()
}

위 함수 두개를 조합해서 이렇게 사용합니다.

var (
    Kernel32dll = windows.NewLazyDLL("Kernel32.dll")
    procSystemParamInfo = Kernel32dll.NewProc("SetLocalTime")
)

시스템 시간을 변경할려면 WinAPI중 SetLocalTime 함수를 사용해야 합니다.

type SYSTEMTIME struct {
    Year         uint16
    Month        uint16
    DayOfWeek    uint16
    Day          uint16
    Hour         uint16
    Minute       uint16
    Second       uint16
    Milliseconds uint16
}

SetLocalTime에서 사용할 SYSTEMTIME 구조체를 선언해줍니다.

ntptime, err := ntp.Time("time.google.com")

NTP 라이브러리로 github.com/beevik/ntp 를 사용했습니다.

st := SYSTEMTIME{}
st.Year = uint16(ntptime.Year())
st.Month = uint16(ntptime.Month())
st.Day = uint16(ntptime.Day())
st.Hour = uint16(ntptime.Hour())
st.Minute = uint16(ntptime.Minute())
st.Second = uint16(ntptime.Second())
st.Milliseconds = uint16(ntptime.Nanosecond() / 1000000)

procSystemParamInfo.Call(uintptr(unsafe.Pointer(&st)))

구한 ntptime을 구조체에 채운뒤 SetLocalTime를 호출하면 됩니다.

이하 전체 코드입니다.

package main

import (
    "encoding/json"
    "fmt"
    "time"
    "unsafe"

    "os"
    "strings"
    "syscall"

    "github.com/beevik/ntp"
    "golang.org/x/sys/windows"
)

// https://anubissec.github.io/How-To-Call-Windows-APIs-In-Golang/#
var (
    Kernel32dll = windows.NewLazyDLL("Kernel32.dll")
    //procSystemParamInfo = Kernel32dll.NewProc("SetSystemTime")
    procSystemParamInfo = Kernel32dll.NewProc("SetLocalTime")
)

type SYSTEMTIME struct {
    Year         uint16
    Month        uint16
    DayOfWeek    uint16
    Day          uint16
    Hour         uint16
    Minute       uint16
    Second       uint16
    Milliseconds uint16
}

// ---------------------------------------------------------------------------------------------
// 관리자 권한 체크
func IsAdmin() bool {
    _, err := os.Open("\\.\PHYSICALDRIVE0")
    if err != nil {
        //관리자 권한이 없음
        return false
    }
    //관리자 권한이 있음.
    return true
}

// ---------------------------------------------------------------------------------------------
func runMeElevated() {
    verb := "runas"
    exe, _ := os.Executable()
    cwd, _ := os.Getwd()
    args := strings.Join(os.Args[1:], " ")

    verbPtr, _ := syscall.UTF16PtrFromString(verb)
    exePtr, _ := syscall.UTF16PtrFromString(exe)
    cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
    argPtr, _ := syscall.UTF16PtrFromString(args)

    // var showCmd int32 = 0 //SW_HIDE
    var showCmd int32 = 5 //SW_SHOW

    err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
    if err != nil {
        panic(err)
    }
}

// ---------------------------------------------------------------------------------------------
func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

type Config struct {
    //DB관련 설정값
    NTP_URL_LIST string `json:"ntp_url_list"`
}

// ---------------------------------------------------------------------------------------------
// LoadConfig 는 설정파일을 읽어들인다.
func LoadConfig(filePath string, result any) bool {

    bytes, err := os.ReadFile(filePath)
    if err != nil {
        fmt.Println(err)

        fo, err := os.Create(filePath)
        checkErr(err)
        defer fo.Close()

        jsonBytes, err := json.MarshalIndent(result, "", "	")
        checkErr(err)

        _, err = fo.Write(jsonBytes)
        checkErr(err)

        fmt.Println(filePath, "파일을 생성했습니다.")
        fmt.Println("json 파일을 열어서 각 항목을 수정하세요")
        return false
    }
    err = json.Unmarshal(bytes, &result)
    checkErr(err)

    return true
}

// ---------------------------------------------------------------------------------------------
func main() {

    //설정파일 값 읽기
    cf := Config{}
    if !LoadConfig("./timesync.json", &cf) {
        //설정파일 읽기 실패시 종료.
        os.Exit(0)
    }

    //관리자 권한 체크
    if IsAdmin() {
        //관리자 권한일 경우
        URLS := strings.Split(cf.NTP_URL_LIST, ",")

        for _, url := range URLS {
            url = strings.Replace(url, " ", "", -1)

            ntptime, err := ntp.Time(url)
            if err != nil {
                fmt.Println(err)
                continue
            }

            st := SYSTEMTIME{}
            st.Year = uint16(ntptime.Year())
            st.Month = uint16(ntptime.Month())
            st.Day = uint16(ntptime.Day())
            st.Hour = uint16(ntptime.Hour())
            st.Minute = uint16(ntptime.Minute())
            st.Second = uint16(ntptime.Second())
            st.Milliseconds = uint16(ntptime.Nanosecond() / 1000000)
            procSystemParamInfo.Call(uintptr(unsafe.Pointer(&st)))

            t := time.Now()
            t2 := t.Sub(ntptime)
            fmt.Printf("변경전 %s 서버와 로컬 시간 차이 : %s n", url, t2)

            time.Sleep(time.Millisecond * 100)

            ntptime, err = ntp.Time(url)
            if err != nil {
                fmt.Println(err)
                continue
            }
            t = time.Now()
            t2 = t.Sub(ntptime)
            fmt.Printf("변경후 %s 서버와 로컬 시간 차이 : %s n", url, t2)
            break
        }
        //------------------------------------------------------------------
        fmt.Println("엔터키 누르면 창이 닫힙니다.")
        fmt.Scanln()

    } else {
        //관리자가 아니면 관리자 권한으로 다시 실행함
        runMeElevated()
    }
}

Comments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다