Accepts Windows network addresses and domain names (not just IPs).
Replaced default tray menus with less cluttered version.
Version 1.1
New command for displaying a menu.
I wanted to make something based on this TCP/IP sample. Either this or a chat seemed like the obvious choices. I wanted to create something that is:[*:222gppje]Different enough from professional apps like RealVNC that it has it's own niche.
[*:222gppje]Not too great a security risk or backdoor-like.So this is what I cam out with.
All of the commands are on the server. So the client can only perform actions you specifically allow (in theory). It works somewhat like the Simple Mouse Gesture script in that you create new commands by adding labels to be bottom of the server script. Without further ado...
Client Script
;AHK Remote Client v1.2 #SingleInstance Force #NoEnv SendMode Input NetworkAddress = 127.0.0.1 ;127.0.0.1 means local machine (so does blank). NetworkPort = 8257 PromptForAddy = 1 ;Allows the user to supply another address if desired. TestTimout = 1000 ;ms, Blank means don't pre-test the address. MaxDataLength = 4096 ;Longest message that can be recieved. MaxGuiRows = 10 ButtonSize = 128 ;Blank means auto. Menu TRAY, Tip, AHK Remote Menu TRAY, Icon, SHELL32.DLL, 121 Menu TRAY, NoStandard If (NOT A_IsCompiled) { Menu TRAY, Add, &Edit, TrayEdit Menu TRAY, Add } Menu TRAY, Add, &Reload, TrayReload Menu TRAY, Add, E&xit, TrayExit If PromptForAddy { Gui Add, Text, w64 Right, Address: Gui Add, Edit, w100 yp-4 x+8 vNetworkAddress, %NetworkAddress% Gui Add, Text, xm w64 Right, Port: Gui Add, Edit, w100 yp-4 x+8 vNetworkPort, %NetworkPort% Gui Add, Button, gGetStarted Default, Connect Gui Show,, Enter Address Return } GetStarted: Gui Submit if (NetworkAddress = "") NetworkAddress := "127.0.0.1" If (NOT TestTimout) TestTimout := 0 NeedIP := !RegExMatch(NetworkAddress, "^(\d+\.){3}\d+$") If (TestTimout OR NeedIP) { ;Use Ping to check if the address is reachable, we can also get the IP address this way. RunWait %ComSpec% /C Ping -n 1 -w %TestTimout% %NetworkAddress% > getpingtestip.txt,, Hide If (ErrorLevel AND TestTimout) { MsgBox %NetworkAddress% cannot be reached. FileDelete getpingtestip.txt ExitApp } If NeedIP { Loop, Read, getpingtestip.txt { If RegExMatch(A_LoopReadLine, "(?<=\[)(\d+\.){3}\d+(?=\])", NetworkAddress) Break } } FileDelete getpingtestip.txt } Menu PopUp, Add, Dummy, HandleMenu ;So the menu exists. If (ButtonSize != "") ButtonSize := "w" . ButtonSize ;v v v v v v v v v v v v v v v v v v v OnExit DoExit MainSocket := PrepareSocket(NetworkAddress, NetworkPort) If (MainSocket = -1) ExitApp ;failed DetectHiddenWindows On Process Exist MainWindow := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel) DetectHiddenWindows Off ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ;FD_READ + FD_CLOSE + FD_WRITE = 35 If DllCall("Ws2_32\WSAAsyncSelect", "UInt", MainSocket, "UInt", MainWindow, "UInt", 5555, "Int", 35) ;v v v v v v v v v v v v v v v v v v v { MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") ExitApp } OnMessage(5555, "ReceiveData", 99) ;Allow 99 (i.e. lots of) threads. Return PrepareSocket(IPAddress, Port) { VarSetCapacity(wsaData, 32) Result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) If ErrorLevel { MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required. return -1 } If Result ; Non-zero, which means it failed (most Winsock functions return 0 upon success). { MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } ;AF_INET = 2 SOCK_STREAM = 1 IPPROTO_TCP = 6 Socket := DllCall("Ws2_32\socket", "Int", 2, "Int", 1, "Int", 6) If (Socket = -1) { MsgBox % "Socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } VarSetCapacity(SocketAddress, 16) InsertInteger(2, SocketAddress, 0, 2) ; AF_INET = 2 InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2) ; sin_port InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4) ; sin_addr.s_addr ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ If DllCall("Ws2_32\connect", "UInt", Socket, "UInt", &SocketAddress, "Int", 16) { Result := DllCall("Ws2_32\WSAGetLastError") If (Result = 10061) MsgBox Connection Refused. That probably means the server script is not running. Else MsgBox % "Connect() indicated Winsock error " . Result return -1 } Return Socket } ReceiveData(wParam, lParam) { Global MaxGuiRows, ButtonSize, MaxDataLength, MainSocket, MenuChoice ;v v v v v v v v v v v v v v v v v v v VarSetCapacity(ReceivedData, MaxDataLength, 0) ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", wParam, "Str", ReceivedData, "Int", MaxDataLength, "Int", 0) If (ReceivedDataLength = 0) ; The connection was gracefully closed Return NormalClose() if ReceivedDataLength = -1 { WinsockError := DllCall("Ws2_32\WSAGetLastError") If (WinsockError = 10035) ; No more data to be read Return 1 If WinsockError = 10054 ; Connection closed Return NormalClose() MsgBox % "Recv() indicated Winsock error " . WinsockError ExitApp } Command := SubStr(ReceivedData, 1, 10) ReceivedData := SubStr(ReceivedData, 11) ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ If (Command = "ARCOMLIST:") { Gui Destroy Loop Parse, ReceivedData, %A_Space% { StringReplace ButtonName, A_Loopfield, _, %A_Space%, All If (Mod(A_Index, MaxGuiRows) = 0) Options .= "ym " Gui Add, Button, %ButtonSize% gHandleButton %Options%, %ButtonName% Options = } Gui Show,, AHK Remote } Else If (Command = "ARSHOWTXT:") { Gui 2:Destroy Gui 2:Add, Edit, Multi w500, %ReceivedData% Gui 2:+ToolWindow +Owner1 Gui 2:Show,, AHK Remote } Else If (Command = "ARMESSAGE:") { Gui +OwnDialogs MsgBox,, AHK Remote, %ReceivedData% } Else If (Command = "ARYESORNO:") { Gui +OwnDialogs MsgBox 36, AHK Remote, %ReceivedData% IfMsgBox Yes SendData(MainSocket, "ARESPONSE:YES") Else SendData(MainSocket, "ARESPONSE:NO") } Else If (Command = "ARGETINFO:") { Gui +OwnDialogs InputBox Result, AHK Remote, %ReceivedData%,,, 130 If (ErrorLevel OR Result = "") SendData(MainSocket, "ARESPONSE:CANCEL") Else SendData(MainSocket, "ARESPONSE:" . Result) } Else If (Command = "ARPASWORD:") { Gui +OwnDialogs InputBox Result, AHK Remote, Password required., Hide,, 110 If ErrorLevel SendData(MainSocket, "ARESPONSE:CANCEL") Else SendData(MainSocket, "ARESPONSE:" . RC4txt2hex("OPENSESAME", Result . ReceivedData)) } Else If (Command = "ARPOPMENU:") { Menu PopUp, DeleteAll Loop Parse, ReceivedData, | Menu PopUp, Add, %A_LoopField%, HandleMenu MenuChoice := 0 Menu PopUp, Show SendData(MainSocket, "ARESPONSE:" . MenuChoice) } Return 1 } NormalClose() { ExitApp Return 1 } ;v v v v v v v v v v v v v v v v v v v SendData(Socket, Data) { SendRet := DllCall("Ws2_32\send", "UInt", Socket, "Str", Data, "Int", StrLen(Data), "Int", 0) If (SendRet = -1) MsgBox % "Send() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") Return SendRet } ;By Laszlo, used by the password function. RC4txt2hex(Data,Pass) { Format := A_FormatInteger SetFormat Integer, Hex b := 0, j := 0 VarSetCapacity(Result,StrLen(Data)*4) Loop 256 { a := A_Index - 1 Key%a% := Asc(SubStr(Pass, Mod(a,StrLen(Pass))+1, 1)) sBox%a% := a } Loop 256 { a := A_Index - 1 b := b + sBox%a% + Key%a% & 255 T := sBox%a% sBox%a% := sBox%b% sBox%b% := T } Loop Parse, Data { i := A_Index & 255 j := sBox%i% + j & 255 k := sBox%i% + sBox%j% & 255 Result .= Asc(A_LoopField)^sBox%k% } Result := RegExReplace(Result, "0x(.)(?=0x|$)", "0$1") StringReplace Result, Result, 0x,,All SetFormat Integer, %Format% Return Result } InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) { Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data. DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF) } GuiClose: ExitApp DoExit: TrayExit: DllCall("Ws2_32\WSACleanup") ExitApp TrayEdit: Edit Return TrayReload: Reload Return ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ HandleButton: StringReplace CommandName, A_GuiControl, %A_Space%, _, All SendData(MainSocket, "ARCOMMAND:" . CommandName) Return HandleMenu: MenuChoice := A_ThisMenuItemPos ReturnServer Script
;AHK Remote Server v1.2 #SingleInstance Force #NoEnv SendMode Input NetworkAddress = 0.0.0.0 ;Listen address, 0.0.0.0 = any NetworkPort = 8257 ;Listen port MaxDataLength = 4096 ;Longest message that can be recieved. Menu TRAY, Tip, AHK Remote Server`n%A_IPAddress1% Menu TRAY, Icon, SHELL32.DLL, 125 Menu TRAY, NoStandard If A_IsCompiled ;The self-reading trick won't work for compiled scripts. CommandList = Quit Volume_Up Volume_Down Mute ;sample hard-coded list Else { LabelStart := False Loop Read, %A_ScriptFullPath% { If (NOT LabelStart) { If InStr(A_LoopReadLine, "===" . "Begin Custom Labels" . "===") LabelStart := True Else Continue } If RegExMatch(A_LoopReadLine, "^[^\s;]\S*(?=:\s*$)", LabelName) CommandList .= " " . LabelName } CommandList := SubStr(CommandList, 2) Menu TRAY, Add, &Edit, TrayEdit Menu TRAY, Add, &Copy Command List, TrayCopy Menu TRAY, Add } Menu TRAY, Add, No Connection, TrayDisconnect Menu TRAY, Disable, No Connection Menu TRAY, Add, &Reload, TrayReload Menu TRAY, Add, E&xit, TrayExit RunningCommands := " " ;v v v v v v v v v v v v v v v v v v v OnExit DoExit MainSocket := PrepareSocket(NetworkAddress, NetworkPort) If (MainSocket = -1) ExitApp ;failed DetectHiddenWindows On Process Exist MainWindow := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel) DetectHiddenWindows Off ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ;FD_READ + FD_CLOSE + FD_ACCEPT = 41 If DllCall("Ws2_32\WSAAsyncSelect", "UInt", MainSocket, "UInt", MainWindow, "UInt", 5555, "Int", 41) ;v v v v v v v v v v v v v v v v v v v { MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") ExitApp } OnMessage(5555, "ReceiveData", 99) ;Allow 99 (i.e. lots of) threads. Return PrepareSocket(IPAddress, Port) { VarSetCapacity(wsaData, 32) Result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) If ErrorLevel { MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required. return -1 } If Result ; Non-zero, which means it failed (most Winsock functions return 0 upon success). { MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } ;AF_INET = 2 SOCK_STREAM = 1 IPPROTO_TCP = 6 Socket := DllCall("Ws2_32\socket", "Int", 2, "Int", 1, "Int", 6) If (Socket = -1) { MsgBox % "Socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } VarSetCapacity(SocketAddress, 16) InsertInteger(2, SocketAddress, 0, 2) ; AF_INET = 2 InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2) ; sin_port InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4) ; sin_addr.s_addr ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ If DllCall("Ws2_32\bind", "UInt", Socket, "UInt", &SocketAddress, "Int", 16) { MsgBox % "Bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } If DllCall("Ws2_32\listen", "UInt", Socket, "UInt", "SOMAXCONN") { MsgBox % "Listen() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } Return Socket } ReceiveData(wParam, lParam) { Global MaxDataLength, OutgoingSocket, CommandList, RunningCommands, ClientResponse Event := lParam & 0xFFFF If (Event = 8) ;FD_ACCEPT = 8 { If (OutgoingSocket > 0) NormalClose() ;Close OutgoingSocket OutgoingSocket := DllCall("Ws2_32\accept", "UInt", wParam, "UInt", &SocketAddress, "Int", 0) If (OutgoingSocket < 0) MsgBox % "Accept() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") Else { SendData(OutgoingSocket, "ARCOMLIST:" . CommandList) Menu TRAY, Rename, No Connection, &Disconnect Menu TRAY, Enable, &Disconnect Menu TRAY, Tip, AHK Remote Server`nConnected! } Return 1 } ;v v v v v v v v v v v v v v v v v v v VarSetCapacity(ReceivedData, MaxDataLength, 0) ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", wParam, "Str", ReceivedData, "Int", MaxDataLength, "Int", 0) If (ReceivedDataLength = 0) ; The connection was gracefully closed Return NormalClose() if ReceivedDataLength = -1 { WinsockError := DllCall("Ws2_32\WSAGetLastError") If (WinsockError = 10035) ; No more data to be read Return 1 If WinsockError = 10054 ; Connection closed Return NormalClose() MsgBox % "Recv() indicated Winsock error " . WinsockError ExitApp } Command := SubStr(ReceivedData, 1, 10) ReceivedData := SubStr(ReceivedData, 11) ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ If (Command = "ARCOMMAND:") { If InStr(" " . CommandList . " ", " " . ReceivedData . " ") { If NOT InStr(" " . RunningCommands . " ", " " . ReceivedData . " ") { RunningCommands .= ReceivedData . " " If IsLabel(ReceivedData) ;Should be redundant, but just in case. GoSub %ReceivedData% StringReplace, RunningCommands, RunningCommands, %A_Space%%ReceivedData%%A_Space%, %A_Space% } } } else If (Command = "ARESPONSE:") ClientResponse := ReceivedData Return 1 } NormalClose() { Global OutgoingSocket, ClientResponse := "DISCONNECT" Result := DllCall("Ws2_32\closesocket", "UInt", OutgoingSocket) If (Result != 0) { MsgBox % "CloseSocket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") ExitApp } OutgoingSocket = Menu TRAY, Tip, AHK Remote Server`n%A_IPAddress1% Menu TRAY, Rename, &Disconnect, No Connection Menu TRAY, Disable, No Connection Return 1 } ;v v v v v v v v v v v v v v v v v v v SendData(Socket, Data) { SendRet := DllCall("Ws2_32\send", "UInt", Socket, "Str", Data, "Int", StrLen(Data), "Int", 0) If (SendRet = -1) MsgBox % "Send() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") Return SendRet } ;By Laszlo, used by the password function. RC4txt2hex(Data,Pass) { Format := A_FormatInteger SetFormat Integer, Hex b := 0, j := 0 VarSetCapacity(Result,StrLen(Data)*4) Loop 256 { a := A_Index - 1 Key%a% := Asc(SubStr(Pass, Mod(a,StrLen(Pass))+1, 1)) sBox%a% := a } Loop 256 { a := A_Index - 1 b := b + sBox%a% + Key%a% & 255 T := sBox%a% sBox%a% := sBox%b% sBox%b% := T } Loop Parse, Data { i := A_Index & 255 j := sBox%i% + j & 255 k := sBox%i% + sBox%j% & 255 Result .= Asc(A_LoopField)^sBox%k% } Result := RegExReplace(Result, "0x(.)(?=0x|$)", "0$1") StringReplace Result, Result, 0x,,All SetFormat Integer, %Format% Return Result } InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) { Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data. DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF) } GuiClose: ExitApp DoExit: TrayExit: DllCall("Ws2_32\WSACleanup") ExitApp TrayEdit: Edit Return TrayReload: Reload Return ;^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ TrayCopy: ClipBoard := CommandList Return TrayDisconnect: NormalClose() Return ; --------------------------------------------------------------------------- ; Here are some functions you can use to communicate with the client script. ARMessage(Text) ;Standard message box. { Global OutgoingSocket SendData(OutgoingSocket, "ARMESSAGE:" . Text) } ARShowText(Text) ;Shows text in a copyable box, for longer test. { Global OutgoingSocket SendData(OutgoingSocket, "ARSHOWTXT:" . Text) } ARYesNo(Text) ;Returns 1 (true) for yes. { Global OutgoingSocket, ClientResponse = SendData(OutgoingSocket, "ARYESORNO:" . Text) Loop { Sleep 50 If (ClientResponse == "DISCONNECT") Exit Else If (ClientResponse != "") Return (ClientResponse == "YES") } } ARInput(Text) ;Returns CANCEL if response is blank or dialog is canceled. { Global OutgoingSocket, ClientResponse = SendData(OutgoingSocket, "ARGETINFO:" . Text) Loop { Sleep 50 If (ClientResponse == "DISCONNECT") Exit Else If (ClientResponse != "") Return ClientResponse } } ARPassword(Pass) ;Asks for a password . { ;Returns 1 (true) for success. If ErrorLevel is 1, it was canceled. Global OutgoingSocket, ClientResponse = Loop 10 { Random Rand, 1, 255 Challenge .= Chr(Rand) } SendData(OutgoingSocket, "ARPASWORD:" . Challenge) Loop { Sleep 50 If (ClientResponse == "DISCONNECT") Exit Else If (ClientResponse != "") { If (ClientResponse = "CANCEL") { ErrorLevel := 1 Return 0 } ErrorLevel := 0 Return (ClientResponse = RC4txt2hex("OPENSESAME", Pass . Challenge)) } } } ARMenu(Items) ;Items format: Item1|Item2|Item3, || makes a separator. { ;Returns item postion (counting separators) or 0 if canceled. Global OutgoingSocket, ClientResponse = SendData(OutgoingSocket, "ARPOPMENU:" . Items) Loop { Sleep 50 If (ClientResponse == "DISCONNECT") Exit Else If (ClientResponse != "") Return ClientResponse } } ; ==========Begin Custom Labels========== (Do not delete this line.) ; Put a comment after a label to prevent it from being a command. Close_Server: If ARYesNo("Are you sure?") ExitApp Return Restart_Server: Reload Return Sound: SoundGet VolLevel SoundGet IsMute,, MUTE SoundInfo := "Vol:" . Round(VolLevel) . "% Mute:" . IsMute Result := ARMenu("Volume Up|Volume Down||Mute|Un-Mute||" . SoundInfo) If (Result = 1) SoundSet +10 Else If (Result = 2) SoundSet -10 Else If (Result = 4) SoundSet 1,, MUTE Else If (Result = 5) SoundSet 0,, MUTE Return View_Server_Code: If A_IsCompiled ;The self-reading trick won't work for compiled scripts. ARMessage("Sorry, you can't. It's compiled. ") Else { FileRead Code, %A_ScriptFullPath% Code := RegExReplace(Code, "`as).*\R(?=.*?===" . "Begin Custom Labels" . "===)") Code := RegExReplace(Code, "i)(ARPassword)\(.*?\)", "$1(""*******"")") ARShowText(Code) VarSetCapacity(Code, 0) } Return Ultra_Basic_Chat: Result := ARInput("Send message:") Loop { If (Result == "CANCEL") Break InputBox Result, AHK Remote Server, %Result%,,, 130 If ErrorLevel Break Result := ARInput(Result) } Return Shutdown_Computer: ;Change the password below, than remove this comment to enable. If ARPassword("swordfish") { MsgBox 49, AHK Remote Server, AHK Remote Client requested shutdown. Permit?, 30 IfMsgBox OK Shutdown 12 Else ARMessage("Shutdown aborted by remote user.") } Else ARMessage("Permission Denied.") ReturnNotice that the last label has a comment after it, this prevents it from becoming a command that the client can use until you remove it. You can do the same thing if you add labels for other purposes, such as like a timer.
On a related note, I made some functions for interacting with the client in basic ways.
[*:222gppje]ARMessage(Text) - Client displays Text in Message Box.
[*:222gppje]ARShowText(Text) - Client displays Text in a copyable text box.
[*:222gppje]ARYesNo(Text) - Client displays Text in Yes/No Message Box. Returns True for Yes.
[*:222gppje]ARInput(Text) - Client displays an Input Box with Text as the prompt. Returns the entered text.
[*:222gppje]ARPassword(Pass) - Client prompts the user for a password and sends it back (encrypted). Returns True if it matches Pass.
[*:222gppje]ARMenu(Items) - Client displays pop-up menu of Items. Returns selected item position.All of them are demonstrated in the sample commands I included.
These scripts are much more sparsely commented than the sample I mentioned, so if you're trying to learn how to do something similar you're probably better of with that. In case you wondered about the v v v and ^ ^ ^ comments, they indicate that the part in between is the same in both scripts.
I think this script is more suitable for use with a computer across the room than across the world but it does work over the internet as well as a LAN. Comments, suggestions, and bug reports are welcome as always.
Incidentally I found in one case the pre-test with ping failed even though the client was able to connect, probably due to firewall rules, so keep in mind that you can turn the pre-test off by setting TestTimout to blank or 0.