INT 21h AH=44h is a family of calls, selected by AL, that ask about or control the device behind a handle or a drive, rather than moving data through it. The one most programs need is AL = 00h, get device information: for the handle in BX it returns a word in DX that says what the handle is connected to.

DX bitA device (bit 7 = 1)A file (bit 7 = 0)
0console input (the keyboard)bits 0-5: the drive, 0 = A, 1 = B, 2 = C
1console output (the screen)
2the NUL device
3the clock device
4fast output through INT 29h
5binary (raw) mode: no Ctrl+C check, no line editing
6end-of-input flagnot written since it was opened
71: a character device0: a disk file
14the driver takes control strings (02h / 03h)
15on a network drive

Bit 7 is the test. A program that wants to know whether its output is redirected asks about handle 1: a device with bit 1 set is the screen, bit 7 = 0 means a file (PROG > OUT.TXT), and a device without bit 1 is NUL or a printer. MORE and programs that draw progress bars use this to decide whether to pause, or whether to draw at all.

prints three lines: "standard output: the console", "NUL: a device, not the console" and "TEST.TXT: a file on drive C"
MOV DX, OFFSET M1
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV BX, 1 ; handle 1, standard output
CALL KIND
MOV AX, 3D01h ; open the NUL device for writing
MOV DX, OFFSET NULN
INT 21h
CALL CHECK
MOV BX, AX
MOV DX, OFFSET M2
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
CALL KIND
MOV AH, 3Eh ; close it
INT 21h
MOV AH, 3Ch ; DOS: create TEST.TXT
XOR CX, CX ; CX = 0: normal attributes
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK ; stop if CF = 1
MOV BX, AX ; BX = the handle
MOV DX, OFFSET M3
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
CALL KIND
MOV AH, 3Eh ; close the handle
INT 21h
MOV AH, 41h ; delete TEST.TXT
MOV DX, OFFSET FNAME
INT 21h
FNAME DB "TEST.TXT", 0
NULN DB "NUL", 0
M1 DB "standard output: $"
M2 DB "NUL: $"
M3 DB "TEST.TXT: $"
KIND PROC NEAR ; print what handle BX is
MOV AX, 4400h ; DOS IOCTL 00h: get device information
INT 21h ; DX = the information word
TEST DL, 80h ; bit 7: a device?
JZ KIND2
TEST DL, 03h ; bits 0 and 1: console input, output
MOV DX, OFFSET KCON ; (MOV leaves the flags alone)
JNZ KIND1
MOV DX, OFFSET KDEV
KIND1:
MOV AH, 09h
INT 21h
RET
KIND2:
AND DL, 3Fh ; a file: bits 0-5 = the drive, 0 = A
ADD DL, "A"
MOV KDRV, DL
MOV DX, OFFSET KFILE
MOV AH, 09h
INT 21h
RET
KIND ENDP
KCON DB "the console", 13, 10, "$"
KDEV DB "a device, not the console", 13, 10, "$"
KFILE DB "a file on drive "
KDRV DB "?", 13, 10, "$"
CHECK PROC NEAR ; CF = 1: print the error code in AL and exit
JNC CHECK1
PUSH AX
MOV DX, OFFSET FAILMSG
MOV AH, 09h
INT 21h
POP AX
CALL PHEXB
MOV AX, 4C01h ; exit with code 1
INT 21h
CHECK1:
RET
CHECK ENDP
FAILMSG DB "failed: error $"
PHEXB PROC NEAR ; print AL as two hex digits
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high digit first
MOV CX, 2
PHEXB1:
AND AL, 0Fh
ADD AL, "0"
CMP AL, "9"
JBE PHEXB2
ADD AL, 7 ; 10..15 -> "A".."F"
PHEXB2:
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AL, DH ; then the low digit
LOOP PHEXB1
POP DX
POP CX
RET
PHEXB ENDP

Seeing redirection

Run with its output redirected to a file, the example above would report a file for standard output. The next one gets that effect without a command line: it points handle 1 at TEST.TXT with INT 21h AH=45h and 46h, as COMMAND.COM does for >, and asks while the redirection is in place. The answer is printed through handle 1, so it lands in the file, and the program shows it once handle 1 is back on the console.

prints: while redirected, handle 1 was: a file on drive C
MOV AH, 3Ch ; create TEST.TXT
XOR CX, CX
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK
MOV SI, AX ; SI = the file
MOV AH, 45h ; keep a copy of standard output
MOV BX, 1
INT 21h
CALL CHECK
MOV DI, AX ; DI = the console, saved
MOV AH, 46h ; handle 1 := the file, as "> TEST.TXT"
MOV BX, SI
MOV CX, 1
INT 21h
CALL CHECK
MOV BX, 1
CALL KIND ; the answer goes into the file
MOV AH, 46h ; handle 1 := the console again
MOV BX, DI
MOV CX, 1
INT 21h
CALL CHECK
MOV AH, 3Eh ; close the saved copy
MOV BX, DI
INT 21h
MOV DX, OFFSET M1
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, 4200h ; rewind the file
MOV BX, SI
XOR CX, CX
XOR DX, DX
INT 21h
MOV AH, 3Fh ; read what KIND wrote there
MOV CX, 64
MOV DX, OFFSET BUF
INT 21h
CALL CHECK
MOV CX, AX
CALL OUT1 ; print it (DX = BUF still)
MOV AH, 3Eh ; close the handle
INT 21h
MOV AH, 41h ; delete TEST.TXT
MOV DX, OFFSET FNAME
INT 21h
FNAME DB "TEST.TXT", 0
M1 DB "while redirected, handle 1 was: $"
BUF DB 64 DUP (?)
KIND PROC NEAR ; print what handle BX is
MOV AX, 4400h ; DOS IOCTL 00h: get device information
INT 21h ; DX = the information word
TEST DL, 80h ; bit 7: a device?
JZ KIND2
TEST DL, 03h ; bits 0 and 1: console input, output
MOV DX, OFFSET KCON ; (MOV leaves the flags alone)
JNZ KIND1
MOV DX, OFFSET KDEV
KIND1:
MOV AH, 09h
INT 21h
RET
KIND2:
AND DL, 3Fh ; a file: bits 0-5 = the drive, 0 = A
ADD DL, "A"
MOV KDRV, DL
MOV DX, OFFSET KFILE
MOV AH, 09h
INT 21h
RET
KIND ENDP
KCON DB "the console", 13, 10, "$"
KDEV DB "a device, not the console", 13, 10, "$"
KFILE DB "a file on drive "
KDRV DB "?", 13, 10, "$"
CHECK PROC NEAR ; CF = 1: print the error code in AL and exit
JNC CHECK1
PUSH AX
MOV DX, OFFSET FAILMSG
MOV AH, 09h
INT 21h
POP AX
CALL PHEXB
MOV AX, 4C01h ; exit with code 1
INT 21h
CHECK1:
RET
CHECK ENDP
FAILMSG DB "failed: error $"
PHEXB PROC NEAR ; print AL as two hex digits
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high digit first
MOV CX, 2
PHEXB1:
AND AL, 0Fh
ADD AL, "0"
CMP AL, "9"
JBE PHEXB2
ADD AL, 7 ; 10..15 -> "A".."F"
PHEXB2:
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AL, DH ; then the low digit
LOOP PHEXB1
POP DX
POP CX
RET
PHEXB ENDP
OUT1 PROC NEAR ; write CX bytes at DS:DX to handle 1
PUSH BX
MOV BX, 1 ; standard output
MOV AH, 40h
INT 21h
POP BX
RET
OUT1 ENDP

Other subfunctions

ALDoes
01hset device information from DX (DH must be 0): bit 5 on CON switches it to raw mode
02h / 03hread / write a control string from / to a character device driver
04h / 05hthe same for the block device of the drive in BL
06hinput status: AL = FFh ready, 00h not (a file: not at its end yet)
07houtput status
08his the drive in BL removable? AX = 0 yes, 1 no
09his the drive in BL on a network? bit 12 of DX
0Ahis the handle in BX on a network? bit 15 of DX
0Dhgeneric block device request (DOS 3.2+): disk geometry, formatting, the volume serial

DOSBox-X leaves bit 6 at 0 for a writable file on a host folder, where MS-DOS sets it until the first write, and CON's bit 6 there can change with the keyboard. Test single bits (7, 1, the drive bits) rather than comparing the whole word.