Articles

Showing posts with label CMD Shell tricks. Show all posts
Showing posts with label CMD Shell tricks. Show all posts

Tuesday, December 11, 2012

How to determine if Windows was shutdown or rebooted


An user on FreeNode recently asked a question regarding how he can run a backup script only if the system is shutting down. At the face of it, it sounds simple, use a shutdown script like people have for years, but the keyword here is only. He needed to be able to run a script if the system was sent a shutdown event, but not run the script if the system was rebooted. This throws a wrench into using a shutdown script because a shutdown script runs regardless of whether the system is rebooted or shutdown. When an application or user initiates a shutdown Windows will write an event to the Event Viewer System log. The event originates from the USER32 event source with an Event ID of 1074. A typical 1074 event looks like this:

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="USER32" /> 
  <EventID Qualifiers="32768">1074</EventID> 
  <Level>4</Level> 
  <Task>0</Task> 
  <Keywords>0x80000000000000</Keywords> 
  <TimeCreated SystemTime="2012-12-05T01:34:03.000000000Z" /> 
  <EventRecordID>6649</EventRecordID> 
  <Channel>System</Channel> 
  <Computer>computer.domain.local</Computer> 
  <Security UserID="S-1-5-21-2671312382-3219971570-2213343823-1105" /> 
  </System>
- <EventData>
  <Data>C:\Windows\system32\shutdown.exe (COMPUTERNAME)</Data> 
  <Data>COMPUTERNAME</Data> 
  <Data>No title for this reason could be found</Data> 
  <Data>0x800000ff</Data> 
  <Data>restart</Data> 
  <Data /> 
  <Data>DOMAIN\username</Data> 
  <Binary>FF000080000000000000000000000000000000000000000000000000000000000000000000000000</Binary> 
  </EventData>
  </Event>

This is the XML view of the event data generated by a reboot request. The part we are interested in is under the EventData node, where it says restart. If it was a shutdown event that was initiated it would say shutdown rather than restart. This is perfect as we now have a way to determine whether or not the system was sent a shutdown or restart request. So how can we get this data programatically via our shutdown script? Simple, we can use the wevtutil utility that ships with Windows Vista and up. The help context for the utility is quite lengthy so I won't post it all here, but you can open a Command Prompt and run wevtutil /? to see all the options. The wevtutil can make use of XPath queries for querying the XML output of an event. We can use an XPath query to query only for the data we need to check whether or not the system was sent a shutdown or restart. The following command will grab all the 1074 events from the USER32 source in the System event log:


C:\>wevtutil qe system /f:text /q:"*[System/EventID=1074]"
Event[0]:
  Log Name: System
  Source: USER32
  Date: 2012-10-05T01:10:59.000
  Event ID: 1074
  Task: N/A
  Level: Information
  Opcode: N/A
  Keyword: Classic
  User: S-1-5-18
  User Name: NT AUTHORITY\SYSTEM
  Computer: computername
  Description:
The process C:\Windows\system32\winlogon.exe (COMPUTERNAME) has initiated the restart of computer COMPUTERNAME on be
half of user NT AUTHORITY\SYSTEM for the following reason: Operating System: Upgrade (Planned)
 Reason Code: 0x80020003
 Shutdown Type: restart
 Comment:

Event[1]:
  Log Name: System
  Source: USER32
  Date: 2012-10-04T18:55:14.000
  Event ID: 1074
  Task: N/A
  Level: Information
  Opcode: N/A
  Keyword: Classic
  User: S-1-5-18
  User Name: NT AUTHORITY\SYSTEM
  Computer: computername
  Description:
The process C:\Windows\System32\shutdown.exe (COMPUTERNAME) has initiated the restart of computer COMPUTERNAME on behalf of u
ser NT AUTHORITY\SYSTEM for the following reason: No title for this reason could be found
 Reason Code: 0x800000ff
 Shutdown Type: restart
 Comment:

...

We use the /f switch here to specify that the output should be in plain text as it defaults to XML. So now we can query for the events we need, but we need a way to sort them and to only grab the last event. We can use the /c and /rd switches to do this. The /c switch takes an unsigned integer as a parameter that tells wevtutil how many events to return. The /rd switch takes a boolean and tells wevtutil do sort them in reverse direction. Now our command looks like this:

C:\>wevtutil qe system /c:1 /rd:true /f:text /q:"*[System/EventID=1074]"
Event[0]:
  Log Name: System
  Source: USER32
  Date: 2012-12-04T19:34:03.000
  Event ID: 1074
  Task: N/A
  Level: Information
  Opcode: N/A
  Keyword: Classic
  User: S-1-5-21-2671312382-3219971570-2213343823-1105
  User Name: DOMAIN\username
  Computer: computer.domain.local
  Description:
The process C:\Windows\system32\shutdown.exe (WIN7) has initiated the restart of computer WIN7 on behalf of user DOMAIN\username for the following reason: No title for this reason could be found
 Reason Code: 0x800000ff
 Shutdown Type: restart
 Comment:

Now all we have to do is parse out the Shutdown Type which is made simple with the for command:


C:\>for /f "tokens=3 delims= " %i in ('wevtutil qe system /c:1 /rd:true /f:text /q:"*[System/EventID=1074]" ^| findstr /c:"Shutdown Type"') do @echo %i
restart

C:\>

Now that we have parsed out the shutdown type we can set it to a variable and then run conditional code based on the value of the variable.

@echo off
for /f "tokens=3 delims= " %%i in ('wevtutil qe system /c:1 /rd:true /f:text /q:"*[System/EventID=1074]" ^| findstr /c:"Shutdown Type"') do (
    set shutdownType=%%i
)
if ["%shutdownType%"]==["shutdown"] (
    :: your shutdown code here
) else (
    :: if not a shutdown, do something else
)

Just to be safe we can make sure the event we are grabbing is the event that was just initiated by using the XPath timediff() function. This will make sure the event was initiated within the last 60 seconds, specified in milliseconds:

@echo off
for /f "tokens=3 delims= " %%i in ('wevtutil qe system /c:1 /rd:true /f:text /q:"*[System/EventID=1074] and TimeCreated[timediff(@SystemTime) >= 60000]" ^| findstr /c:"Shutdown Type"') do (
    set shutdownType=%%i
)
if ["%shutdownType%"]==["shutdown"] (
    :: your shutdown code here
) else (
    :: if not a shutdown, do something else
)

Thursday, March 15, 2012

Get your public IP from the win32 command-line

Sometimes it's beneficial to be able to retrieve your public routable IP address via the command line so that you can use it in scripts. Using only native tools in Windows this is not possible. I do have an old script that uses a hybrid batch and VBScript solution that uses telnet to connect to a PHP script that dumps back the REMOTE_ADDR super global, but it was clunky and didn't always work reliably. Below is a hybrid batch and powershell script that can do it. Of course this could be done with only powershell, but I wrote it this way so I could use it in both powershell and batch scripts.

Here is the code:

@echo off
powershell -encodedcommand KABuAGUAdwAtAG8AYgBqAGUAYwB0ACAAcwB5AHMAdABlAG0ALgBuAGUAdAAuAHcAZQBiAGMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AYwBoAGUAYwBrAGkAcAAuAGEAbAB0AGUAcgB2AGkAcwB0AGEALgBvAHIAZwAvAGkAcAAuAHAAaABwACIAKQA=

Here is the decoded base64 powershell code:

(new-object system.net.webclient).downloadstring("http://checkip.altervista.org/ip.php")


Tuesday, January 10, 2012

CMD Shell tricks

Recently there was a post on the alt.msdos.batch.nt news group where someone needed to get an IP address into a variable. Sounds easy enough, but the problem he was having was that he couldn't use typical parsing with the findstr command because the language settings were not consistent between systems.

The easiest way to get your local IP address into a variable is like this:

# for /f "tokens=2 delims=[]:" %i in ('ping -n 1 -4 %computername% ^| findstr /i "pinging"') do set ip=%i& echo %ip%


This works fine most of the time, but what if you have multiple interfaces on your system? Consider this:



# for /f "tokens=2 delims=[]:" %i in ('ping -4 -n 1 %computername% ^| findstr /i
"pinging"') do set ip=%i& echo %ip%
169.254.238.27


#


What is that address? That's an APIPA address that Microsoft so graciously provided us in case we were unable to receive an IP address from a DHCP server. This isn't our actual IP address that we are using on the LAN, so this isn't the output we want.

Going back to our original problem, the findstr /i "pinging" won't work because the language was different. So, we need another way to determine this, while also maintaining backwards compatibility with older systems such as Windows 9x systems. Here is my solution to the problem:



# for /f "tokens=3 delims=:" %i in ('arp -a ^| findstr /n /l ":" ^| find "2:"')
do @for /f %z in ('echo %i') do @set ip=%z


#echo %ip%
172.16.2.13


#






Wednesday, December 14, 2011

Get the MAC Address of All Systems on Your Subnet Remotely

Several years ago I interning and was tasked with updating the DHCP reservations on a FreeBSD DHCP server. The Network Administrator told me to go around to each machine and write down the IP Address and it's MAC Address. I thought to myself, there's no way in hell I'm walking around to every system in the building and doing that. How did I do it? I wrote a handy script called getMAC.bat:


@echo off
setlocal enabledelayedexpansion
if [%1] == [] (
  echo.
  echo Please specify a Subnet or IP address.
  echo Example: 192.168.1.0 or 192.168.1.10
  goto :eof
)
arp -d > nul
for /f "tokens=1-4 delims=." %%a in ('echo %1') do (
if not "%%d"==0 (
ping -n 1 -w 5 %1 > nul
for /f "tokens=1,2 delims= " %%x in ('arp -a ^| findstr "%1"') do (
echo.&&echo %%x %%y
goto :eof
)
)
for /l %%i in (1 1 254) do (
set ip=%%a.%%b.%%c.%%i
ping -n 1 -w 5 !ip! > nul
if [!errorlevel!]==[0] (
call :_print !ip!
)
)
)
:_print
for /f "tokens=1,2 delims= " %%x in ('arp -a ^| findstr "%1"') do (
echo %%x %%y
)

Sample run:

getMAC 192.168.1.0
192.168.1.1     00-21-29-b9-22-9d
192.168.1.3     00-26-4a-ee-84-c4
192.168.1.5     00-23-a5-00-06-a2
192.168.1.7     e0-cb-4e-39-b6-40
192.168.1.10    00-0c-29-53-3f-6f
192.168.1.11    00-0c-29-2e-c2-ab

You could also redirect the output to a file for later parsing:
getMAC 192.168.1.0 > outfile.txt

You can also specify just a single IP address to retrieve the MAC Address for:
getMAC 192.168.1.7
e0-cb-4e-39-b6-40

And people say batch scripts aren't useful.

How does it work? It works by pinging each machine and then comparing that IP address against the local machines ARP table to see if it exists. If it does, it prints the IP address and its corresponding MAC address.

This turned manual task that would have taken a couple hours into just a couple minutes of waiting for the script to finish.
Keep in mind this was a fairly small network with roughly 100 nodes. It was a /24 (255.255.255.0) network on a 192.168.10.0 subnet. A couple more things to keep in mind is that I set the ICMP ECHO REPLY timeout to 5 milliseconds with the -w 5 switch to speed up the script. The caveat with this is that any node that takes longer than 5ms to reply will not be found. This can easily be adjusted if your network has higher latency.

Obviously this will only work on a /24. Perhaps if I get tasked with something similar I'll update the script to work on additional networks like 10.0.0.0/18, but at that point I'd be trying to write a subnet calculator in pure batch. : ) This was just an exercise in batch scripting to see what I could come up.

dcprom0