I have a little program for a test that will be put in my big program.
So I want to enter a IP address into a set /p var= varible but it can only be numbers. Not letter. i.e if I were to enter facebook.com it would throw an error i.e echo Invaild and pause
So if I enter a IP in the IP structer 192.168.1.4 it will echo Valid. but if I enter a bunch of numbers like 544564212.545 not in the IP structer it will call the error lable/ marker thing (:marker) and echo Invaild. SO it must only work on a IP structer string.
Thanks you guys for all your help. My program will be availble in a few days. Thanks you agian for your help.
Try this:
set /p var=Enter IP address:
set var=%var: =%
echo %var%|findstr /rc:"^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$" >nul
if %errorlevel% equ 0 (
echo Valid IP address.
) else (
echo Not a valid IP address.
)
I made a slightly more complex script. You shoud call it from the command line: <script.cmd> IP, eg. <script.cmd> 192.168.1.4.
#echo off &setlocal enabledelayedexpansion
set "var=%~1"
set "flag="
for /f "tokens=1-4delims=." %%i in ("%var%") do set /a var1=%%i, var2=%%j, var3=%%k, var4=%%l
for /l %%i in (1,1,4) do if %%i neq 1 (set "var0=!var0!.!var%%i!") else set "var0=!var%%i!"
for /l %%i in (1,1,4) do if "!var%%i!" equ "" set "flag=1"
if not defined flag for /l %%i in (1,1,4) do if !var%%i! lss 0 set "flag=1"
if not defined flag for /l %%i in (1,1,4) do if !var%%i! gtr 255 set "flag=1"
if defined flag (echo "%var0%" is NOT a valid IP.) else echo "%var0%" is a valid IP.
endlocal
Related
Need your expert advice on the below:
Here is the code:
Note: serial is set to value of 100 which is default and it is pulled from the another script where all the server details are stored.
setlocal enabledelayedexpansion
SET serverlist=
SET env=TBD
if /I "%2" EQU "D" (set env=dev&& set env_dir=dev)
if /I "%2" EQU "U" (set env=uat&& set env_dir=uat)
if /I "%2" EQU "P" (set env=prod&& set env_dir=prod)
echo Here are the server details for %env% %1:
echo These are for DEV:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_DEVser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_DEVser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo These are for UAT:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_UATser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_UATser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo These are for PROD:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_PRODser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_PRODser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo details of all the server:
echo %serverdetails%
Current output:
These are for DEV:
D00123 (testing1)
D00456 (testing2)
D00789 (testing3)
These are for UAT:
UAT001 (UAT-env1)
UAT002 (UAT-env2)
UAT003 (UAT-env3)
These are for PROD:
PRD001 (PRD-env1)
PRD002 (PRD-env2)
PRD003 (PRD-env3)
details of all the server:
D00123 testing1 D00456 testing2 D00789 testing3 UAT001 UAT-env1 UAT002 UAT-env2 UAT003 UAT-env3 PRD001 PRD-env1 PRD002 PRD-env2 PRD003 PRD-env3
Question:
For the details of all the server output: I would like to get the output below
D00123
D00456
D00789
UAT001
UAT002
UAT003
PRD001
PRD002
PRD003
If the Dev server details are requested, then UAT / PROD details should not be visible
similarly, if PROD is requested, then DEV and UAT should not be visible.
can you please help me with this?
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
:: Dummy data established outside of routine
SET "serial=100"
SET "ser_=xyz"
SET "dev_%serial%_dev%ser_%1_=D00123 something1,testing1"
SET "dev_%serial%_dev%ser_%16_=D00456 something2,testing2"
SET "dev_%serial%_dev%ser_%22_=D00789 something3,testing3"
SET "dev_%serial%_dev%ser_%35_=D00987 something4,dup"
SET "uat_%serial%_uat%ser_%1_=UAT123 something11,testing4"
SET "uat_%serial%_uat%ser_%16_=UAT456 something12,testing5"
SET "uat_%serial%_uat%ser_%22_=UAT789 something13,dup"
SET "uat_%serial%_uat%ser_%35_=UAT987 something14,testing7"
SET "prod_%serial%_prod%ser_%1_=PRD123 something21,dup"
SET "prod_%serial%_prod%ser_%16_=PRD456 something22,testing9"
SET "prod_%serial%_prod%ser_%22_=PRD789 something23,testing10"
SET "prod_%serial%_prod%ser_%35_=PRD987 something24,testing11"
SET |FIND /i "something"
IF /i "%1"=="d" SET "env=dev"
IF /i "%1"=="u" SET "env=uat"
IF /i "%1"=="p" SET "env=prod"
for %%e in (dev uat prod) do (
SET "report="
if /i "%%e"=="%env%" SET "report=Y"
IF DEFINED report echo These are for %env%:
FOR /l %%o IN (1,1,50) DO if defined %%e_%serial%_%%e%ser_%%%o_ (
for /f "tokens=2,3,4 delims==, " %%u in (
'set %%e_%serial%_%%e%ser_%%%o_'
) do if %%w NEQ dup set "%%eserverdetails=!%%eserverdetails! %%u"&IF DEFINED report echo %%u
)
SET "serverdetails=!serverdetails! !%%eserverdetails!"
)
echo These are for ALL:
FOR %%e IN (%serverdetails%) DO ECHO %%e
GOTO :EOF
Still not really clear on the values in the variables. Test setup shown. Routine accepts d, u or p as first param.
Don't know what ser_ is set to.
Your code contains for..%%a within a for..%%a - decidedly not good practice.
Speaking of which, a couple of proved "good practice" principles:
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. If the syntax set var="value" is used, then the quotes become part of the value assigned.
Prefer to avoid ADFNPSTXZ (in either case) as metavariables (loop-control variables) ADFNPSTXZ are also metavariable-modifiers which can lead to difficult-to-find bugs (See for/f from the prompt for documentation)
So...
Having established env, process the variables for each of the three possibilities (in %%e). Filter the values for %%e _ %serial% _ %%e %ser_% %%o _ where %%o is 1..50 and append to %%eserverdetails if the third token of the variable is not dup. If %%e matches %env% then set the report flag which controls whether the data is echoed.
Finally, list the serverdetails data.
Of course, it would also be possible to list !%env%serverdetails! for the individual lists, which would make the report flag and reporting the data within the %%u loop redundant.
Append each %%eserverdetails to serverdetails after each %%e is processed
I am trying to create a batch script that will ping a machine by hostname, save ip and domain to a variable and display the state (ON or OFF).
How can i fix this code so it will display the status correctly?
My code always returns ON even if pc is actually OFF.
#echo off
setlocal ENABLEDELAYEDEXPANSION
set hostname=non-existent-hostname
set domain=UNRESOLVED
set ip=UNRESOLVED
for /f "tokens=2,3 delims= " %%b in ('ping -a -4 -n 1 !hostname! ^| find "Pinging"') do (
set domain=%%b
set ip=%%c
)
if errorlevel 1 (
echo !hostname! !ip! [!domain!] is OFF
) else (
echo !hostname! !ip! [!domain!] is ON
)
pause
I have another approach using this kind of ping, because my machine is French.
For example, this batch script pings a list of URLs and displays their status (ON/OFF) with different colors.
The URLs are defined in the "URLS" variable.
The script loops through the URLs, formats them using a StringFormat function, gets the IP address of each URL using the "ping" command, and then displays the status of each URL (ON or OFF) with a color using a PSColor function.
#echo off
Title Multi-Ping hosts with colors
Set URLS="non-existent-hostname","https://www.google.com","Nothingtotest","https://www.yahoo.com","www.reddit.com",^
"http://www.wikipedia.com","www.stackoverflow.com","www.bing.com","NoBodyHere.com"
setlocal ENABLEDELAYEDEXPANSION
for %%a in (%URLS%) do (
Call :StringFormat "%%~a"
set "ip="
for /f "tokens=2 delims=[]" %%b in ('ping -n 1 !URL!') do set "ip=%%b"
ping -n 1 !URL!>nul && set "msg=!URL! - !ip! ON" && Call :PSColor "!msg!" Green \n || set "msg=!URL! - !ip! OFF" && Call :PSColor "!msg!" Red \n
)
pause & Exit
::---------------------------------------------------------------------------------
:StringFormat <URL>
(
echo Function StringReplace(Str^)
echo Str = Replace(Str,"http://",""^)
echo Str = Replace(Str,"https://",""^)
echo StringReplace = str
echo End Function
echo wscript.echo StringReplace("%~1"^)
)>"%tmp%\%~n0.vbs"
for /f "delims=" %%a in ('Cscript /nologo "%tmp%\%~n0.vbs"') do ( set "URL=%%~a" )
If Exist "%tmp%\%~n0.vbs" Del "%tmp%\%~n0.vbs"
exit /b
::---------------------------------------------------------------------------------
:PSColor <String> <Color> <NewLine>
If /I [%3] EQU [\n] (
Powershell Write-Host "`0%~1" -ForegroundColor %2
) Else (
Powershell Write-Host "`0%~1" -ForegroundColor %2 -NoNewLine
)
Exit /B
::---------------------------------------------------------------------------------
I figured it out, thanks everyone for hints.
#echo off
setlocal ENABLEDELAYEDEXPANSION
set hostname=non-existent-hostname
set domain=UNRESOLVED
set ip=UNRESOLVED
for /f "tokens=2,3 delims= " %%b in ('ping -a -4 -n 1 !hostname! ^| find "Pinging"') do (
set domain=%%b
set ip=%%c
)
ping -n 1 %hostname% >nul
if %errorlevel% == 0 (
echo %hostname% %ip% [%domain%] is ON
) else (
echo %hostname% %ip% [%domain%] is OFF
)
pause
This batch file loops through a list of computer IP addresses and passwords with a comma separator (IP_List.txt). Commands are then run to connect remotely to each computer. I've got another list of IP's which are exceptions and need to be skipped, so I've got a variable (set str=) which lists these IP's and I'm using findstr to search for them:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
set str=172.16.66.1 172.16.66.2 172.16.66.3 172.16.66.4
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
#ECHO %IP% | findstr "%str%" >nul
IF NOT ERRORLEVEL 1 (ECHO+ & ECHO %IP% & ECHO This IP address was skipped) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
---- (remainder of batch file here) ----
There was a problem with findstr in that having 172.16.66.2 on the exception list, findstr was mistakenly finding a match with the following IP's on my overall list: 172.16.66.224, 172.16.66.225, 172.16.66.226, etc.
After changing the code and putting the list of IP exceptions in a text file (IP_Exceptions.txt) I was able to get past the findstr problem and this modified batch file works without issue:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
::~~ Add a right bracket to the end of the IP address variable
SET IPstr=%IP%]%x%
set match=N
::~~ Loop through a list of IP address exceptions
::~~ (A left bracket has been added to the beginning of
::~~ each IP on the list in order to use it as a delimiter)
::~~ Add a right bracket to the end of the variable "%%j"
::~~ in order to look for a match with the findstr command
for /F "delims=[" %%i in (IP_Exceptions.txt) do (
for /F "tokens=1" %%j in ("%%i") do (
ECHO %%j] | findstr "%IPstr%" >nul
IF not errorlevel 1 set match=Y
)
)
IF %match% == Y (ECHO+ & ECHO %IP% & ECHO Skipped this machine) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
:: ---- (remainder of batch file here) ----
The specifics of getting the findstr section to work correctly without false matches (adding brackets to variables, nested for loops with delimiters, etc.) was kind of clumsy though. Any suggestions on making that section more efficient would be greatly appreciated.
A modified batch file works but it's clumsy:
#ECHO OFF
setlocal EnableDelayedExpansion
Pushd "%~dp0"
Set Log=LogFile.log
FOR /F "tokens=1,2 delims=," %%i in (IP_List.txt) do call :process %%i %%j
(ECHO+ & ECHO --- END OF REPORT ---) >> %Log%
GOTO :EOF
:process
set IP=%1
set PW=%2
::~~ Add a right bracket to the end of the IP address variable
SET IPstr=%IP%]%x%
set match=N
::~~ Loop through a list of IP address exceptions
::~~ (A left bracket has been added to the beginning of
::~~ each IP on the list in order to use it as a delimiter)
::~~ Add a right bracket to the end of the variable "%%j"
::~~ in order to look for a match with the findstr command
for /F "delims=[" %%i in (IP_Exceptions.txt) do (
for /F "tokens=1" %%j in ("%%i") do (
ECHO %%j] | findstr "%IPstr%" >nul
IF not errorlevel 1 set match=Y
)
)
IF %match% == Y (ECHO+ & ECHO %IP% & ECHO Skipped this machine) >> %Log% & GOTO :EOF
#ECHO Processing %IP% >> %Log%
:: ---- (remainder of batch file here) ----
Your subroutine could look like this:
...
:process
echo/ %str% |findstr /LC:" %1 " >nul && (
echo %1 skipped
goto :eof
)
echo processing %1 with %2
:: ---- (remainder of batch file here) ----
Note that I reversed the "search" logic (to make it easier to process spaces correctly)
Instead of echo <sub>|findstr <whole>, I switched to echo <whole> |findstr <sub>
Note: the spaces in echo/ %str% |findstr /LC:" %1 " are critical (they are also compared to avoid false positives).
/LC: tells findstr to search literal (treating the dot as a dot instead of a wildcard) and including spaces.
I'm trying to make a batch script which is supposed to ping a site, log the results, and start a program if the results were negative. This is a modification of original script (not mine), which can be found here. Values of domain, IP, and program variables are for illustrative purposes.
#echo off
cls
set domain=testsite.com
set IP=133.78.17.101
set program=c:\windows\notepad.exe
set output=c:\log.txt
set result=1
:Start
IF [%result%]==[] (
>>%output% echo -----------
start %program%
)
ECHO Pinging %domain%...
FOR /F "delims=" %%G in ('ping -n 1 %domain% ^| find "Reply"') DO SET result=%%G
IF NOT [%result%]==[] (
goto Success
) ELSE (
goto TryAgain
)
:TryAgain
ECHO %domain% unreachable. Trying again...
FOR /F "delims=" %%G in ('ping -n 1 %domain% ^| find "Reply"') DO SET result=%%G
IF NOT [%result%]==[] (
goto Success2
) ELSE (
goto TryIp
)
:TryIp
ECHO %domain% unreachable. Pinging %ip%...
FOR /F "delims=" %%G in ('ping -n 1 %IP% ^| find "Reply"') DO SET result=%%G
IF NOT [%result%]==[] (
goto SuccessDNS
) ELSE (
goto TestInternet
)
:TestInternet
ECHO %ip% unreachable. Testing internet connection.
FOR /F "delims=" %%G in ('ping -n 1 www.google.com ^| find "Reply"') DO SET result=%%G
IF NOT [%result%]==[] (
goto Success3
) ELSE (
goto NetDown
)
:Success
>>%output% ECHO Connected
>>%output% echo %date% %time% %result%
ping -n 3 127.0.0.1 > nul
goto Start
:Success2
>>%output% ECHO Connected with packet loss.
>>%output% echo %date% %time% %result%
set result=
ping -n 3 127.0.0.1 > nul
goto Start
:Success3
>>%output% ECHO Domain %domain% not reachable. Connected via IP.
>>%output% echo %date% %time% %result%
set result=
ping -n 3 127.0.0.1 > nul
goto Start
:SuccessDNS
>>%output% ECHO DNS problem.
>>%output% echo %date% %time% %result%
set result=
ping -n 3 127.0.0.1 > nul
goto Start
:NetDown
>>%output% ECHO No internet connection.
>>%output% echo %date% %time% %result%
set result=
ping -n 3 127.0.0.1 >nul
goto Start
What I'm trying to achieve is this - if anything other than a perfect reply to a ping request is received, the script should start a program. To secure that this happens only then, I've been clearing the result variable every time, other than on an expected ping response.
Echoing the value of result keeps returning 1, even after I've emptied it.
in your line
FOR /F "delims=" %%G in ('ping -4 -n 1 %domain% ^| find "Reply"') DO SET result=%%G
%%G is either not defined (when the word Reply doesn't occur), which doesn't touch your Result variable at all,
or a line like Reply from x.x.x.x : Bytes=32 Time<1ms TTL=128, which definitively isn't empty.
According to the rest of your code, you probably meant ... DO SET "result=" to unset the variable.
Note: searching for "Reply" is
a) language dependent ("Antwort" on German Windows) and
b) not reliable (think of Reply from localhost: destination address unreachable).
Better search for TTL= (even works without a for loop):
ping -n 1 %IP% | find "TTL=" >nul && set "reply=true" || set "reply=false"
echo %reply%
Just an addendum to Stephan's post.
In addition to reasons posted in Stephan's post, the code was not working as I had originally planned it to, because the comparisons were failing as well. If there was a successful ping, the result variable was being set to a string consisting of multiple words, which was breaking the comparison. To avoid this, I had to change every instance of
IF [%result%]==[]
to
IF ["%result%"]==[""]
Thomas Weller's (now deleted) comment was also on the right track - I did need to empty the result variable in :Start:
:Start
IF ["%result%"]==[""] (
>>%output% echo -----------
start %program%
)
SET result=
ECHO Pinging %domain%...
This emptying was needed to annul any previous successes and the original set result=1 (in case of consistent failures).
I only want to find specific lines in his text file, so i figure that using range would be a good idea. But i can seem to find any tutorial online. please help..
An example of the text file
XXX scan report for 192.0.0.0
exampleexampleexampleexampleexample
OS: windows 8
exampleexampleexampleexample
exampleexampleexampleexample
PORT STATE SERVICE VERSION
21/tcp close ftp
80/tcp open http Microsoft ISS
exampleexampleexampleexample
exampleexampleexampleexample
XXX scan report for 192.0.0.1
exampleexampleexampleexampleexample
exampleexampleexampleexample
exampleexampleexampleexample
PORT STATE SERVICE VERSION
21/tcp close ftp
80/tcp open http Microsoft ISS
exampleexampleexampleexample
exampleexampleexampleexample
I wanted to get IP address, OS Details and Port status into a spreadsheet.
My code:
#echo off
set file=C:\Users\1.txt
set match=report for
set match1=OS
findstr /c:"%match%" %file%
findstr /c:"%match1%" %file%
findstr /c:"tcp " /c:"udp " %file%
for /f "tokens=1-5" %%a in ('findstr /c:"%match%" %file%') do (
echo "IP Address:","%%e" >> report1.csv
for /f "tokens=1,2 delims=:*" %%a in ('findstr /c:"%match1%" %file%') do
(
echo "Operating System: ","%%b" >> report1.csv
echo "PORT","STATE","SERVICE","VERSION" >> report1.csv
for /f "tokens=1-4 delims=: " %%a in ('findstr /c:"tcp " /c:"udp "
%file%') do (
echo "%%a","%%b","%%c","%%d" >> report1.csv
)
)
)
There is a big problem with this code and it is in the for loop.
The code will get the first ip then will proceed to get the os details, but not ever ip have the os details, so the os details will be placed in the wrong ip.
Another problem with the code is that it will list all os and port details under one ip address. And the next ip address will also be the same, it will have all the os and port details as well.
Please do help me to solve this problem. Or is there any other method like call?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43335765.txt"
SET "outfile=%destdir%\outfile.txt"
>"%outfile%" ECHO IP,OS,PORT,STATUS,SERVICE,VERSION
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
CALL :process %%a
)
GOTO :EOF
:process
ECHO %*|FIND "scan report for " >NUL
IF NOT ERRORLEVEL 1 GOTO newip
IF "%~1"=="OS:" SET "$os=%*"&GOTO :eof
ECHO %~1|FIND "/">NUL
IF ERRORLEVEL 1 GOTO :EOF
FOR /f "tokens=1,2,3*" %%p IN (
"%*") DO >>"%outfile%" ECHO %$ip%,%$os:*: =%,%%p,%%q,%%r,%%s
GOTO :eof
:newip
IF "%~2" neq "" shift&GOTO newip
SET "$ip=%~1"
SET "$os=: "
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q43335765.txt containing your data for my testing.
Produces the file defined as %outfile%
Having established the filenames, put the header line in the output file and process each line of the file through :process
In :process, detect the key string indicating a ne data item. If found, go to :newip which simply shuffles the data line along until only the last entry remains and assign this last entry to $ip. Set $ip to :Space + any special string you want to represent "not found" (like unknown for instance)
If the line doesn't contain the keystring, see whether the first token is OS:, and set $os to the entire original line if it is.
Otherwise, lookk for a / in the first token. If it's not there, abandon processing this line, otherwise simply tokenise the line, selecting the first to thid and rest and output using the saved ip, the saved os, except for the part before the first :Space and the four data line entries, all separated by commas.
Revision to cater for | in data - replace with /
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43335765.txt"
SET "outfile=%destdir%\outfile.txt"
>"%outfile%" ECHO IP,OS,PORT,STATUS,SERVICE,VERSION
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
SET "line=%%a"
CALL :preprocess
)
GOTO :EOF
:preprocess
SET "line=%Line:|=/%"
CALL :process %line%
GOTO :eof
:process
ECHO %*|FIND "scan report for " >NUL
IF NOT ERRORLEVEL 1 GOTO newip
IF "%~1"=="OS:" SET "$os=%*"&GOTO :eof
ECHO %~1|FIND "/">NUL
IF ERRORLEVEL 1 GOTO :EOF
FOR /f "tokens=1,2,3*" %%p IN (
"%*") DO >>"%outfile%" ECHO %$ip%,%$os:*: =%,%%p,%%q,%%r,%%s
GOTO :eof
:newip
IF "%~2" neq "" shift&GOTO newip
SET "$ip=%~1"
SET "$os=: "
GOTO :eof
Shows the need to provide a representative data sample...
With characters that have special meaning like |, assign to a variable and call a preprocessor to convert all | to / (or whatever else is desired) and then call the process with the result.
Formula: set "var=%somevar:string1=string2%"
will assign to var the value of somevar with all occurrences of string1 replaced by string2. The enclosing quotes in a set command ensure that any stray trailing spaces on the line are not included in the value assigned.
The flaw in your logic is that each time you execute findstr, it starts back at line 1 of your text file. If you're working with multi-line records, you have to build your line of output with in one pass of the record. I suggest a for /F loop and testing tokens is better suited to this than findstr. Here's a suggestion:
#echo off
setlocal
set "file="C:\Users\1.txt"
>"report1.csv" (
echo "IP Address","Operating System","port 21","port 80"
for /F "usebackq tokens=1-3,5" %%I in ("%file%") do (
rem // check if line begins a record
if /i "%%~J"=="scan" if /i "%%~K"=="report" (
set "IP=%%~L"
set "OS="
)
rem // check if line contains OS
if /i "%%~I"=="OS:" set OS="%%~J %%~K"
rem // check if line contains port 21
if /i "%%~I"=="21/tcp" set "state21=%%~J"
rem // port 80 indicates end of needed info in record
setlocal enabledelayedexpansion
if /i "%%~I"=="80/tcp" echo(!IP!,!OS!,!state21!,%%~J
endlocal
)
)