Home | Info | Research | Blog | Repos | Messages | Contact Me

 


PowerShell에는 PowerShell ISE라는 개발도구가 포함되어 있습니다. 이 개발도구를 이용하면 Visual Stuio와 같이 브레이크 포인트를 설정하고 한줄 단위로 실행해 볼 수 있습니다.

브레이크 포인트를 설정하고 싶은 줄에서 F9키를 누르면됩니다. 하지만 루프를 돌 때마다 브레이크 포인트가 걸린다면 디버깅 하기가 어려울 때가 많습니다. 이럴때에는 특정 값일 때에만 브레이크 포인트가 걸리도록 조건 브레이크 포인트를 설정하면 됩니다.

PowerShell ISE에서 gci.ps1라는 이름으로 아래 스크립트를 저장합니다.

$list = Get-ChildItem C:\Windows

foreach ($i in $list)
{
    Write-Output $i.Name
}


PowerShell ISE에도 일반 PowerShell 프롬프트와 같이 명령을 입력할 수 있는 곳이 있습니다. 이곳에 아래와 같이 입력하여 조건 브레이크 포인트를 설정합니다.

Set-PSBreakpoint -Line 5 -Script gci.ps1 -Action {if ($i.Name -match "notepad.exe") { break }}
(두줄이 아니고 한줄입니다.)

-Line 5는 스크립트 상에서 브레이크 포인트를 설정할 줄 번호입니다. $i.Name이 notepad.exe와 같으면 브레이크가 걸리도록 설정하였습니다.

이제 PowerShell ISE에서 F5를 눌러 스크립트를 실행합니다.



스크립트가 실행되면 그림과 같이 $i.name이 notepad.exe일 때 브레이크 포인트가 걸립니다.

PowerShell ISE가 아니더라도 PowerShell 프롬프트에서도 똑같이 브레이크 포인트를 설정할 수 있습니다. 이때에는 .\gci.ps1를 입력하여 스크립트를 실행하면 브레이크 포인트가 걸립니다. 그리고 브레이크 포인트가 걸리면 디버그 모드에 진입할 수 있습니다. 디버그 모드에서는 트레이스를 하려면 일일이 명령을 입력해야 하므로 PowerShell ISE를 이용하는 것이 편리합니다.




Windows Server 2008에서는 기본적으로 PowerShell 스크립트의 실행을 막아놓았습니다.

PowerShell의 스크립트 실행 설정인 Set-ExecutionPolicy RemoteSigned 같은 명령으로 설정 해준 뒤에, 직접 cmd.exe나 PowerShell 프롬프트에서 스크립트의 경로를 입력하여 실행을 하면 별 문제없이 실행이 됩니다.
 
하지만 프로그램을 이용하여 실행할 때(ShellExecuteEx 같은 프로세스 실행 API를 이용)에는 Set-ExecutionPolicy 설정을 해도 전혀 스크립트가 실행되지 않습니다.

그렇기 때문에 Windows Server 2008에서 막아놓은 스크립트 실행 설정을 풀어주어야 합니다.

시작->gpedit.msc 입력

로컬 그룹 정책 편집기가 실행됩니다.

컴퓨터 구성 -> 관리 템플릿 -> Windows 구성 요소 -> Windows PowerShell 선택 후 오른쪽 창에서 스크립트 실행 켜기(Turn On Script Execution)를 더블클릭 합니다.

1. 사용(E) 체크
2. 옵션의 실행 정책에서 로컬 스크립트 및 서명된 원격 스크립트 허용 선택

확인을 눌러 창을 닫으면 설정이 완료됩니다.






오늘은 PYRASIS.COM(피라시스닷컴)이 문을 연지 7년째 되는 날입니다.

요즘은 SNS의 시대이기도 하다 보니 블로그에는 좀 뜸해진게 사실입니다.

앞으로도 유익한 정보를 담기 위해 노력하겠습니다. 감사합니다.



PowerShell은 윈도우의 배치파일을 대체하기 위해 나왔습니다. 그렇기 때문에 기존의 배치파일 명령어도 사용할 수 있습니다. 하지만 PowerShell로 작업을 하기로 마음을 먹었다면 배치파일 스타일 대신 PowerShell 스타일로 만들어보면 좋겠죠.

echo "Hello PowerShell"
Write-Output "Hello PowerShell"
Write-Host "Hello PowerShell"

echo 정겨운 명령어입니다. 배치파일에서 문자열을 출력할 때 사용합니다. 사실 PowerShell에서 지원하는 배치파일 명령어들은 PowerShell cmdlet의 Alias(별칭)입니다. 마찬가지로 echo도 Write-Output의 별칭입니다.

Write-Output과 Write-Host의 다른 점은 Write-Host는 글자색과 배경색을 지정할 수 있다는 것입니다.

dir > $nul
Get-ChildItem | Out-Null

dir의 출력을 Null로 리다이렉션하는 명령들입니다. 배치파일에서 Null은 nul로 표시하지만 PowerShell에서는 $nul로 표시합니다. >과 같은 리다이렉션 대신 | 파이프를 이용해도 됩니다. Out-Null은 말그대로 출력 결과를 Null로 보내버립니다.

dir
Get-ChildItem

mkdir Hello
New-Item -Type Directory -Path Hello

cd C:\Windows
Set-Location C:\Windows

dir 대신에 Get-ChildItem을, mkdir 대신 New-Item을 사용해봅니다. New-Item 부분이 좀 옵션이 있어서 복잡하긴 하지만...

디렉터리 변경 명령어인 cd는 Set-Location으로 사용할 수 있습니다.




  • 2009년 말부터 지금까지 많은 일들이 있었습니다. 다니던 회사도 옮기게 되었고, 새 회사로 오고 나서도 우여곡절이 많았습니다. 다행히 프로젝트를 매우 체계적으로 진행하는 곳에서 일을 할 수 있게 되었습니다.

  • 그 무엇보다도 소프트웨어 개발팀은 팀장이 매우 중요하다는 것을 몸소 체험할 수 있었습니다.
TAG 근황



안녕하세요. 이재홍입니다.

윈도우 프로젝트 필수 유틸리티 Subversion, Trac, CruiseControl.NET에는 420페이지 "날짜를 버전으로 사용하기" 부분이 있습니다.

이제 2010년이 되었습니다. 하지만 책에 나와있는 PowerShell 스크립트 rcversion-date.ps1, tracversion-date.ps1은 2009년까지만 정상 동작하도록 되어 있습니다.

자세한 수정 방법은 윈도우 프로젝트 필수 유틸리티 추가팁 2010년 이후 PowerShell 스크립트 설정을 참고하세요.





오늘은 PYRASIS.COM(피라시스닷컴)이 문을 연지 6년째 되는 날입니다.

어쩌다 보니 5주년은 그냥 넘어가버렸습니다.

앞으로 더 나은 기술 정보를 제공할 수 있도록 노력하겠습니다. 감사합니다.



포팅 작업을 할 때 의외로 중요한 부분이 시간에 관련된 부분입니다. 별것 아닌것 같지만 특정 시간 주기로 루틴이 실행된다던가, 데이터에 시간이 포함된다면 시간 함수 처리를 잘못하면 포팅을 하고 나서도 제대로 동작이 되지 않을 때가 많습니다.

먼저 포팅에 앞서 유저 모드와 커널 모드의 시간 처리 기준에 대해 이해할 필요가 있습니다.
 - 유저 모드 : 밀리초, 100나노초
 - 커널 모드 : 100나노초

따라서 유저 모드와 커널 모드의 처리 기준이 다르다면 변환이 필요합니다. 하지만 대부분의 시간 함수는 변환 없이 사용할 수 있습니다. 이 변환기준은 대부분 동기화 객체의 대기 타임아웃 처리 정도에 사용됩니다.

typedef struct _FILETIME {
    DWORD dwLowDateTime;
    DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;

void GetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime)
{
    KeQuerySystemTime((PLARGE_INTEGER)&lpSystemTimeAsFileTime);
}

FILETIME과 같은 64비트 형태의 시간 구조체는 100나노초 단위입니다. 커널 모드에서도 64비트 형태의 LARGE_INTEGER 구조체를 사용하고 있으므로 변환 절차 없이 바로 사용할 수 있습니다.

typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

void GetSystemTime(LPSYSTEMTIME lpSystemTime)
{
    LARGE_INTEGER systemTime;
    TIME_FIELDS timeFields;

    KeQuerySystemTime(&systemTime);

    RtlTimeToTimeFields(&systemTime, &timeFields);
}

SYSTEMTIME 구조체와 같이 연,월,일, 시, 분, 초, 요일을 세분화 한 것은 먼저 64비트 형식의 시간을 구한 다음 RtlTimeToTimeFields과 같은 함수로 변환해주면 됩니다.

#include <time.h>

errno_t localtime_s(struct tm * _Tm, const time_t * _Time)
{
    LARGE_INTEGER systemTime, localTime;
    TIME_FIELDS timeFields;

    KeQuerySystemTime(&systemTime);
    ExSystemTimeToLocalTime(&systemTime, &localTime);

    RtlTimeToTimeFields(&localTime, &timeFields);

    _Tm->tm_sec = timeFields.Second;
    _Tm->tm_min = timeFields.Minute;
    _Tm->tm_hour = timeFields.Hour;
    _Tm->tm_mday = timeFields.Day;
    _Tm->tm_mon = timeFields.Month;
    _Tm->tm_year = timeFields.Year;
    _Tm->tm_wday = timeFields.Weekday;

    return 0;
}

WinAPI가 아닌 CRT 함수의 경우 해당 CRT 함수의 구조체에 맞게 필드를 채워주어야 합니다. 먼저 64비트 형태의 시간을 구한 다음 ExSystemTimeToLocalTime 함수를 이용하여 로컬 타임으로 변환합니다. 그리고 RtlTimeToTimeFields으로 연,월,일... 형태로 변환한 뒤 tm 구조체에 맞게 값을 채워줍니다.



Windows Driver Kit(WDK) 7.0.0 정식 버전이 발표되었습니다.

이번 WDK 7.0.0은 Windows 7에 대응하는 버전입니다. 7.0.0 버전 부터는 Windows 2000 지원이 제외되었습니다.

그동안 Microsoft Connect를 통해서 WDK 베타 버전을 받을 수 있었지만, 이제부터 Microsoft Connect를 통하지 않고 Microsoft Download 페이지에서 직접 받을 수 있습니다.

WDK 공식 페이지 : http://www.microsoft.com/whdc/DevTools/WDK/WDKpkg.mspx

WDK 다운로드 페이지 : http://www.microsoft.com/downloads/details.aspx?FamilyID=2105564e-1a9a-4bf4-8d74-ec5b52da3d00&displaylang=en
TAG WDK, Windows 7



커널 모드 드라이버에서 큰 비중을 차지하는 부분은 아무래도 동기화 객체 부분일 것입니다.

유저 모드에서 사용할 수 있는 동기화 객체는 커널 오브젝트인 것도 있고 아닌 것도 있습니다. 커널 오브젝트로 존재하는 동기화 객체는 커널 모드 함수를 사용하여 구현을 하고, 그렇지 않은 경우 대체할 수 있는 함수를 사용하여 구현합니다.

하지만 어떻게 해서든 유저 모드와 동일하게 구현하기 어려운 경우가 있습니다. 이 때에는 불가피하게 원본 소스 코드를 수정하여, 커널 모드에 적합하게 동기화 객체를 사용해야 합니다.

CreateMutex
HANDLE
CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwner,
    LPCWSTR lpName)
{
    LARGE_INTEGER timeOut = {0, };
    PRKMUTEX mutex = ExAllocatePoolWithTag(NonPagedPool, sizeof(KMUTEX), 'xtum');

    KeInitializeMutex(mutex, 0);

    if (bInitialOwner == TRUE)
        KeWaitForSingleObject(mutex, Executive, KernelMode, FALSE, &timeOut);

    return (HANDLE)mutex;
}

뮤텍스는 커널 모드 동기화 객체입니다. 따라서 커널 모드 함수를 그대로 사용합니다. 리턴하는 변수는 실제 핸들 값이 아니라 뮤텍스 오브젝트라는 점을 주의합니다. 즉 원본 소스코드 수정을 최소화 하기 위해서입니다.

단 뮤텍스를 사용하면서 이름을 만들어 서로 구분해주었다면, 복잡하게 CreateMutex 함수를 구현할 필요 없이 원본 소스 코드를 수정하여 상황에 맞게 커널 모드 동기화 함수를 직접 사용해야 합니다.

CreateSemaphore
HANDLE
CreateSemaphore(
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    LONG lInitialCount,
    LONG lMaximumCount,
    LPCSTR lpName)
{
    PRKSEMAPHORE semaphore =
        ExAllocatePoolWithTag(NonPagedPool, sizeof(KSEMAPHORE), 'ames');

    KeInitializeSemaphore(semaphore, lInitialCount, lMaximumCount);

    return (HANDLE)semaphore;
}

세마포어 또한 커널 모드 동기화 객체이므로 동일한 방법으로 구현합니다. 단 이름을 만들어야 할 경우 뮤텍스와 마찬가지로 원본 소스 코드를 수정합니다.

CloseHandle
BOOL
CloseHandle(
    HANDLE hObject)
{
    if (MmIsAddressValid(hObject) == TRUE)
        ExFreePool(hObject);
    else
        ZwClose(hObject);

    return TRUE;
}

 앞서 CreatreMutex에서 뮤텍스 오브젝트를 위해 동적으로 메모리를 할당했습니다. 그러므로 메모리를 해제해주어야 하는데, 기존에 ZwCreateFile과 같은 함수를 이용하여 커널 핸들을 만들었을 수 있습니다. 이런 경우를 대비하여 커널 모드 핸들이면 ZwClose 함수로 핸들을 닫아주고, 메모리 주소라면 해제 해주도록 합니다. ObIsKernelHandle이라는 유용한 함수가 있지만 안타깝게도 비스타 부터 사용이 가능합니다. 아쉽지만 MmIsAddressValid 함수를 이용하여 메모리 주소인지 판단합니다. 핸들이 아무리 숫자가 크더라도 메모리로 판단될 일은 없습니다.

WaitForSingleObject
DWORD
WaitForSingleObject(
    HANDLE hHandle,
    DWORD dwMilliseconds)
{
    NTSTATUS status;
    LARGE_INTEGER timeOut;
    LONG milliseconds = dwMilliseconds;
    DWORD ret;

    timeOut.QuadPart = -(milliseconds * 10000);

    if (dwMilliseconds == INFINITE)
        status = KeWaitForSingleObject(hHandle, Executive, KernelMode, FALSE, NULL);
    else
        status = KeWaitForSingleObject(hHandle, Executive, KernelMode, FALSE, &timeOut);

    switch (status)
    {
    case STATUS_SUCCESS:
        ret = WAIT_OBJECT_0;
        break;
    case STATUS_TIMEOUT:
        ret = WAIT_TIMEOUT;
        break;
    case STATUS_ABANDONED_WAIT_0:
        ret = WAIT_ABANDONED;
        break;
    default:
        ret = WAIT_FAILED;
    }

    return 0;
}

대기 함수입니다. WaitForSingleObject의 동작을 흉내내기 위해 리턴값도 유저 모드와 동일한 형태로 만들어줍니다.

여기서 주의해야 할 점은 Timeout 값 입니다. 유저 모드에서는 이 값을 밀리초 단위로 사용하지만, 커널 모드에서는 100 나노초 단위로 사용합니다. 따라서 밀리초를 100나노초 단위로 변환해주어야 합니다. 또한 유저 모드에서는 상대 시간만 사용하지만, 커널 모드에서는 절대 시간과 상대 시간을 모두 사용합니다. Timeout 값이 양수일 때에는 절대 시간이며 음수 일 때 상대 시간입니다. 그러므로 밀리초에 10000을 곱하여 100나노초 단위로 만들어준 다음, 음수로 변환하여 상대 시간으로 만들어줍니다.

Critical Section
typedef struct _CRITICAL_SECTION {

    ERESOURCE Resource;

} CRITICAL_SECTION, *LPCRITICAL_SECTION;

VOID
InitializeCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection)
{
    ExInitializeResourceLite(&lpCriticalSection->Resource);
}

VOID
EnterCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection)
{
    KeEnterCriticalRegion();
    ExAcquireResourceExclusiveLite(&lpCriticalSection->Resource, TRUE);
}

VOID
LeaveCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection)
{
    ExReleaseResourceLite(&lpCriticalSection->Resource);
    KeLeaveCriticalRegion();
}

VOID
DeleteCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection)
{
    ExDeleteResourceLite(&lpCriticalSection->Resource);
}

Critical Section은 유저 모드 동기화 객체입니다. 따라서 커널 모드에는 대응되는 것이 없습니다. 이 Critical Section은 Critical Region과 리소스를 이용하여 구현합니다.

Event
HANDLE
CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,
    BOOL bManualReset,
    BOOL bInitialState,
    LPCSTR lpName)
{
    EVENT_TYPE eventType;
    PRKEVENT event = ExAllocatePoolWithTag(NonPagedPool, sizeof(KEVENT), 'tnve');

    if (bManualReset == TRUE)
        eventType = NotificationEvent;
    else
        eventType = SynchronizationEvent;

    KeInitializeEvent(event, eventType, bInitialState);

    return (HANDLE)event;
}

BOOL
SetEvent(
    HANDLE hEvent)
{
    KeSetEvent(hEvent, IO_NO_INCREMENT, FALSE);

    return TRUE;
}

BOOL
ResetEvent(
    HANDLE hEvent)
{
    KeResetEvent(hEvent);

    return TRUE;
}

BOOL
PulseEvent(
    HANDLE hEvent)
{
    KePulseEvent(hEvent, IO_NO_INCREMENT, FALSE);

    return TRUE;
}

이벤트 또한 커널 모드 동기화 객체입니다. CreateEvent 함수에서 객체 이름 부분은 구현되지 않았습니다. 이름을 정해 사용하려면 IoCreateNotificationEvent, IoCreateSynchronizationEvent 함수를 이용하면 됩니다. (활용도는 그다지 놓지 않겠지만)

지금까지 유저 모드 동기화 함수들의 기능을 커널 모드 함수를 사용하여 구현을 하였습니다. 하지만 커널 모드에서 성능을 높이기 위해서는 스핀락과 같은 동기화 객체를 이용하는 것이 좋습니다.

스핀락을 사용하려면 원본 소스 코드를 적절히 수정해야 합니다. 단 주의애햐 할 점은 스핀락을 사용하면 IRQL이 DISPATCH_LEVEL이 되기 때문에 PagedPool 메모리나 DISPATCH_LEVEL에서 사용할 수 없는 함수가 있는지 살펴봐야 합니다.