Powershell MSUtil.LogQuery not closing SQL connection - powershell

I've written a PowerShell scrip that iterates through a large number of IIS W3C logfiles and inserts the values into a MSSQL database.
Set-Variable -Name "UnprocessedDir" -Value "X:\files" -Description "Folder for unprocessed log files" Scope Script
Set-Variable -Name "InputObject" -Value (New-Object -comObject MSUtil.LogQuery.IISW3CInputFormat) -Description "Log Parser input COM object" -Scope Script
Set-Variable -Name "OutputObject" -Value (New-Object -comObject MSUtil.LogQuery.SQLOutputFormat) -Description "Log Parser output COM object" -Scope Script
$OutputObject.clearTable = $false
$OutputObject.createTable = $false
$OutputObject.database = "Database_Name"
$OutputObject.driver = "SQL Server"
$OutputObject.dsn = "DSN_Name"
$OutputObject.fixColNames = $true
$OutputObject.ignoreIdCols = $true
$OutputObject.ignoreMinWarns = $true
$OutputObject.maxStrFieldLen = 511
$OutputObject.oConnString = $null
$OutputObject.password = $null
$OutputObject.server = "sqlserver.domain.com\INSTANCENAME"
$OutputObject.transactionRowCount = 5000
$OutputObject.username = $null
Set-Variable -Name "IISLogs" -Value #(Get-ChildItem -Path $UnprocessedDir -Recurse -File) -Description "Array of files to be imported into SQL" -Scope Script
Set-Variable -Name "LPComObj" -Value (New-Object -com MSUtil.LogQuery) -Description "COM Object used to import Log Parser records into MSSQL" -Scope Script
Write-Output "$(Get-ISOTimeStamp) Beginning SQL import. $($IISLogs.Count) Files to be imported"
$IISLogs | ForEach-Object { $loop = 0 } {
Set-Variable -Name "SubDir" -Value $(($_.FullName).Split('\')[-2]) -Description "Subdirectory where log file is located" -Scope Script
Set-Variable -Name "LogType" -Value $(($_.FullName).Split('\')[-3]) -Description "Type of log being imported" -Scope Script
Set-Variable -Name "ServerName" -Value $(($_.FullName).Split('\')[-4]) -Description "ServerName of file being imported" -Scope Script
Set-Variable -Name "LPQuery" -Description "Query to use in Log Parser" -Scope Script -Value #"
SELECT
-- FIELDS LogFilename,LogRow,date,time,c-ip,cs-username,s-sitename,s-computername,s-ip,s-port,cs-method,cs-uri-stem,cs-uri-query,sc-status,sc-substatus,sc-win32-status,sc-bytes,cs-bytes,time-taken,cs-version,cs-host,cs(User-Agent),cs(Cookie),cs(Referer),s-event,s-process-type,s-user-time,s-kernel-time,s-page-faults,s-total-procs,s-active-procs,s-stopped-procs
-- STANDARD FIELDS date,time,s-ip,cs-method,cs-uri-stem,cs-uri-query,s-port,cs-username,c-ip,cs(User-Agent),cs(Referer),sc-status,sc-substatus,sc-win32-status,time-taken
'$($ServerName)' as [servername],
'$($_.Name)' as [filename],
LogRow AS [row],
'$($LogType)' as [logtype],
TO_TIMESTAMP(date,time) AS [timestamp],
[s-ip],
[cs-method],
[cs-uri-stem],
[cs-uri-query],
TO_INT([s-port]),
[cs-username],
[c-ip],
[cs(User-Agent)],
[cs(Referer)],
TO_INT([sc-status]),
TO_INT([sc-substatus]),
TO_INT([sc-win32-status]),
TO_INT([time-taken]),
0 AS lock
INTO IIS_W3C
FROM '$($_.FullName)'
"#
Set-Variable -Name "LPResult" -Value ($LPComObj.ExecuteBatch($LPQuery, $InputObject, $OutputObject)) -Description "IIS Log File imported into SQL" -Scope Script
If ($LPResult -eq $false)
{
Write-Output "$(Get-ISOTimeStamp) Data imported from `"$($_.FullName)`""
Set-Variable -Name "loop" -Value ($loop + 1) -Description "Increase loop iteration Count" -Scope Script
}
Else
{
Write-Output "$(Get-ISOTimeStamp) Log Parser returned errors importing `"$($_.FullName)`""
Throw "$(Get-ISOTimeStamp) Log Parser returned errors importing `"$($_.FullName)`""
}
}
The number of logs I'm importing is tens of thousands; the code above works spectacularly for a few hundred files, but after a few hours, it crashes. From what I can tell, it looks like every iteration of the ForEach-Object loop creates a new SQL TCP connection which is not terminated at the end of the loop.
I've tried creating the $LPComObj both within and outside of the loop. I've tried Remove-Variable. I've tried some generic commands like $LPComObj.Close(), .Remove(), .Quit(), etc. The MSUtil.LogQuery method itself does not seem to contain any methods to close the SQL TCP connection, and as the script is running, I can see more and more TCP connections piling up. I tried a few things using [System.Runtime.Interopservices.Marshal]:: to release/remove the COM object, but none of them closed the TCP connection. Even closing the PowerShell session doesn't kill the connection.
The only way I was able to do this is by hunting down and finding the dllhost.exe" process that was using the ports and killing it. But from within the script, there isn't a clean way to get the PID of the offending dllhost.exe process. (Trying to kludge some variant of Get-Process | Stop-Process might work, but would add a lot of time to the execution of the script.)
What other ways might I be able to work around this problem?

Related

Powershell ignore errors or warnings

I had downloaded a powershell module called SecurityFever. In this lib there is a code part:
# Get the global impersonation context
$globalImpersonationContext = Get-Variable -Name 'ImpersonationContext' -Scope 'Global'
# Global variable to hold the impersonation context
if ($null -eq $globalImpersonationContext) {
$stack = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]'
New-Variable -Name 'ImpersonationContext' -Value $stack -Option ReadOnly -Scope Global -Force
}
it is absolutely clear what it is and what it wants to do, but when I execute this I got red lines in the console window:
Get-Variable : Cannot find a variable with the name
'ImpersonationContext'. At SecurityFever.psm1:2427 char:35
+ ... onContext = Get-Variable -Name 'ImpersonationContext' -Scope 'Global' ...
I think it is because the get-variable does not found this global variable for the 1st time. I am wondering
how to suppress this error without modifying the external lib source code,
or how to do a thing like this - checking if a variable exists or not.
I tried to create the expected global variable before the function call
ImpersonationContext = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]'
or
$global:ImpersonationContext = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]'
but somehow then it becomes a kind of PSVariable, and the code says
Method invocation failed because [System.Management.Automation.PSVariable] does not contain a method named 'Pop'.
I am not an expert in PowerShell, and I can say I totally can't understand why. :(
From here, you can use Test-Path with a special syntax to check for your variable. You can do something like this -
if (Test-Path variable:global:ImpersonationContext)
{
write-host "The variable ImpersonationContext exists in the global scope"
}
else
{
$stack = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]'
New-Variable -Name 'ImpersonationContext' -Value $stack -Option ReadOnly -Scope Global -Force
}

Add logging quickly to an existing set of powershell scripts

I've got a collection of powershell scripts, some of which call others. Some of these subscripts can also be called on their own as needed. How can I quickly add logging to all of the scripts so that any script invocation results in a log file for later examination?
There are a number of questions dealing with logging with some great answers, like this one. But I wanted to see what we could come up with that:
required minimal touching of the existing powershell files
automatically dealt with script A.ps1 calling script B.ps1. If you call
A.ps1, A.ps1 needs to start and finish the logging. But if you call B.ps1
directly, B.ps1 does.
I came up with my answer below, and wanted to share and see if there were other ideas on how to approach this, or suggestions for improvement on my answer.
The support code I write (further down) allows for just adding the following to each ps1 file. It automatically gives me logging regardless of if a script is called at top-level or by another script:
#any params for script
. "$PSScriptRoot\ps_support.ps1"
StartTranscriptIfAppropriate
try
{
#all of the original script
}
finally
{
ConditionalStopTranscript
}
The code that powers this is in ps_support.ps1, sitting next to my collection of powershell files that need logging. It uses Get-Variable and Set-Variable to manipulate a couple variables at the caller's scope level:
Logging__TranscriptStarted is normal so sub-scopes can see that
logging is already happening and not try to start it again.
Logging__TranscriptStartedPrivate is private so a scope can know if
it is responsible for stopping the logging.
Here is ps_support.ps1:
Set-Variable -name TranscriptStartedPropertyName -opt ReadOnly -value 'Logging__TranscriptStarted'
Set-Variable -name TranscriptStartedPrivatePropertyName -opt ReadOnly -value 'Logging__TranscriptStartedPrivate'
function StartTranscriptIfAppropriate
{
$transcriptStarted = [bool](Get-Variable -name $TranscriptStartedPropertyName -ErrorAction Ignore)
if (-not $transcriptStarted)
{
$callstack = get-pscallstack
$fullPath = $callstack[$callstack.count-2].ScriptName
$name = Split-Path -Path $fullPath -Leaf
$directory = Split-Path -Path $fullPath
$logDirectory = [IO.Path]::GetFullPath("$directory\..\scripts_logs")
md -force $logDirectory | out-null
$logFinalPath = "$logDirectory\$(Get-Date -Format o | foreach {$_ -replace ":", "."})_$name.log"
Set-Variable -scope 1 -name $TranscriptStartedPropertyName -value $True
Set-Variable -scope 1 -option private -name $TranscriptStartedPrivatePropertyName -value $True
Start-Transcript $logFinalPath | Write-Host
}
$immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnly
Write-Host "Starting script at $immediateCallerPath"
}
function ConditionalStopTranscript
{
$immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnly
Write-Host "Stopping script at $immediateCallerPath"
$transcriptStartedByMe = [bool](Get-Variable -scope 1 -name $TranscriptStartedPrivatePropertyName -ErrorAction Ignore)
if ($transcriptStartedByMe)
{
Stop-Transcript | Write-Host
}
}

Powershell script making swf extension open with internet explorer

I am trying to figure out how to write a powershell script that will set all .swf extensions to open up on Internet Explorer. I was trying to do this with a command prompt similar to the example below. Unfornately my boss is requiring this to be done through powershell. Any help with this would be greatly appreciated since I have a txt file that will loop through about 400 computers and need to make these changes on.
CMD Way
C:\>ASSOC .swf
.swf=ShockwaveFlash.ShockwaveFlash
C:\>FTYPE ShockwaveFlash.ShockwaveFlash
ShockwaveFlash.ShockwaveFlash="C:\bin\FlashPlayer.exe" %1
What I am Trying:
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
cmd /c assoc .swf=InternetExplorer.Application
### Will the above line automatically install on every pc? ###
}
Any help with trying to insert how to change the FTYPE in powershell so that $computer can cycle through would be greatly appreciated!
ASSOC and FTYPE are CMD.exe built-in commands, not executables, which means they can only be run in the context of CMD. The easiest way to run them is to invoke CMD from PowerShell.
cmd /c assoc .swf
cmd /c ftype ShockwaveFlash.ShockwaveFlash
If you need a "pure" PowerShell implementation, then you need to go to the registry. ASSOC and FTYPE merely write to the registry under theHKEY_CLASSES_ROOT hive. PowerShell does not have a default PSDrive for HKCR:, but that hive is also accessible under HKLM:\Software\Classes.
$ext = '.swf'
$HKCR = 'HKLM:\Software\Classes'
$ftype = Get-ItemProperty -Path "$HKCR\$ext" | select -expand '(default)'
$commandLine = Get-ItemProperty -Path "$HKCR\$ftype\shell\open" | select -expand '(default)'
$commandLine
To update these values, you simply use Set-ItemProperty on the same path.
Set-ItemProperty -Path "$HKCR\$ext" -Name '(default)' -Value 'ShockwaveFlash.ShockwaveFlash'
This requires you to run with Admin privileges. This also assumes that the key already exists. If not, you will have to create it with New-Item
if (-not (Test-Path "$HKCR\$ext")) {
New-Item -Path "$HKCR\$ext"
}
However, if all you want to do is set .swf files to open in iexplore.exe, then retrieving the values is unnecessary, as is modifying the FTYPE key. You need only change the extension association to InternetExplorer.Application instead of ShockwaveFlash.ShockwaveFlash. The following full scripts will do this:
In Batch file:
assoc .swf=InternetExplorer.Application
In PowerShell:
cmd /c assoc .swf=InternetExplorer.Application
In "pure" PowerShell, by modifying the registry:
$key = "HKLM:\Software\Classes\.swf"
$defaultName = '(default)'
$newValue = 'InternetExplorer.Application'
if (-not (Test-Path $key)) {
New-Item -Path $key
}
Set-Itemproperty -Path $key -Name $defaultName -Value $newValue
Note that modifying the registry doesn't take effect immediately. You need to also send a WM_SETTINGCHANGE event, or simply restart explorer.exe (eg: by logging off). You can find code to send the event here, but usually this isn't a problem for automated scripts because they force the user to re-login anyway.

Shell execute in lua

powershell script
Set-ExecutionPolicy Unrestricted
## NEEDED FOR IIS CMDLETS
Import-Module WebAdministration
## CREATE FTP SITE AND SET C:\inetpub\ftproot AS HOME DIRECTORY
New-WebFtpSite -Name "test" -Port "21" -Force
cmd /c \Windows\System32\inetsrv\appcmd set SITE "test" "-virtualDirectoryDefaults.physicalPath:C:\inetpub\ftproot"
## SET PERMISSIONS
## Allow SSL connections
Set-ItemProperty "IIS:\Sites\test" -Name ftpServer.security.ssl.controlChannelPolicy -Value 0
Set-ItemProperty "IIS:\Sites\test" -Name ftpServer.security.ssl.dataChannelPolicy -Value 0
## Enable Basic Authentication
Set-ItemProperty "IIS:\Sites\test" -Name ftpServer.security.authentication.basicAuthentication.enabled -Value $true
## Set USer Isolation
Set-ItemProperty "IIS:\Sites\test" -Name ftpserver.userisolation.mode -Value 3
#Set-ItemProperty "IIS:\Sites\test" -Name ftpServer.security.userIsolation. -Value $true
## Give Authorization to Administrators and grant "read"/"write" privileges
Add-WebConfiguration "/system.ftpServer/security/authorization" -value #{accessType="Allow";roles="";permissions="Read,Write";users="*"} -PSPath IIS:\ -location "test"
## Give Authorization to All Users
#appcmd set config %ftpsite% /section:system.ftpserver/security/authorization /+[accessType='Allow',permissions='Read,Write',roles='',users='*'] /commit:apphost
## Restart the FTP site for all changes to take effect
Restart-WebItem "IIS:\Sites\test"
that I want to run it with lua
I make that script
function Create_iis()
tl1="Set-ExecutionPolicy Unrestricted"
tl2="Import-Module WebAdministration"
tl3="New-WebFtpSite -Name \"test\" -Port \"21\" -Force"
tl4="cmd \/c \\Windows\\System32\\inetsrv\\appcmd set SITE \"test\" \"-virtualDirectoryDefaults.physicalPath:C:\\inetpub\\ftproot\""
tl5="Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.ssl.controlChannelPolicy -Value 0"
tl6="Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.ssl.dataChannelPolicy -Value 0"
tl7="Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.authentication.basicAuthentication.enabled -Value $true"
tl8="Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpserver.userisolation.mode -Value 3"
tl9="Add-WebConfiguration \"\/system.ftpServer\/security\/authorization\" -value \#\{accessType=\"Allow\";roles=\"\";permissions=\"Read\,Write\";users=\"*\"} -PSPath IIS:\ -location \"test\""
tl10="Restart-WebItem \"IIS:\\Sites\\test\""
file = io.open("c:\\testiis.ps1","w");
file:write(tl1.."\n"..tl2.."\n"..tl3.."\n"..tl4.."\n"..tl5.."\n"..tl6.."\n"..tl7.."\n"..tl8.."\n"..tl9.."\n"..tl10.."\n"..);
file:close("c:\\testiis.ps1");
result = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe", "open", "c:\\testiis.ps1", "", SW_HIDE,true);
if result ~=0 then
Dialog.Message("Error", "Error ", MB_OK, MB_ICONSTOP, MB_DEFBUTTON1);
end
end
and work well
but I need to check for every line in this script if return error then exit
and give me the line that make the error
so I make stupid script that
function Create_iis()
result1 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Set-ExecutionPolicy Unrestricted")
result2 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Import-Module WebAdministration")
result3 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","New-WebFtpSite -Name \"test\" -Port \"21\" -Force" )
result4 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","cmd \/c \\Windows\\System32\\inetsrv\\appcmd set SITE \"test\" \"-virtualDirectoryDefaults.physicalPath:C:\\inetpub\\ftproot\"")
result5 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.ssl.controlChannelPolicy -Value 0")
result6 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.ssl.dataChannelPolicy -Value 0")
result7 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpServer.security.authentication.basicAuthentication.enabled -Value $true")
result8 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Set-ItemProperty \"IIS:\\Sites\\test\" -Name ftpserver.userisolation.mode -Value 3")
result9 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Add-WebConfiguration \"\/system.ftpServer\/security\/authorization\" -value \#\{accessType=\"Allow\";roles=\"\";permissions=\"Read\,Write\";users=\"*\"} -PSPath IIS:\ -location \"test\"" )
result10 = Shell.Execute("C:\\WINDOWS\\system32\\windowspowershell\\v1.0\\powershell.exe","open","Restart-WebItem \"IIS:\\Sites\\test\"")
end
and check for every result
but there are some commands that depend on each so it make error
it is impossible to make that ?
sorry for poor English
Thanks in advance
You can use io.popen to call a command in Lua:
local file = assert(io.popen('/bin/ls -la', 'r'))
local output = file:read('*all')
file:close()
print(output) -- > Prints the output of the command.
source: https://stackoverflow.com/a/5243210/1069083

Get-ScheduledTask in PowerShell on Windows Server 2003

I have the below PowerShell code to validate if a scheduled task exists and to check its state.
$SchTsk = Get-ScheduledTask | Select Name, State | ? {$_.Name -eq $SchTask}
If ($SchTsk -ne $Null)
{
Write-Host "SchTask $SchTask exists"
If ($SchTsk.State -eq 3)
{
Write-Host "SchTask State: READY!"
}
}
The code works fine on Windows Server 2008 but does not work on Windows Server 2003. In 2003 I get an error:
New-Object : Cannot load COM type Schedule.Service.
From what I've read it seems that the Schedule.Service COM object does not exist on Server 2003.
So...is there a work-around for this issue to validate a scheduled task and its state on Server 2003?
The following is a sample PowerShell script that reads from the COM object mentioned above and outputs some Task Schedule Information:
#Connecting to COM Object
$schedService = New-Object -ComObject Schedule.Service
$schedService.Connect($Computer)
# Get Scheduled Tasks on Root Folder (Task Scheduler Library)
$folder = $SchedService.GetFolder("")
$tasks = $folder.GetTasks("")
# Output Task Details
$tasks | % {
"-" * 40
"Task " + $_.Name + ":"
"-" * 40
$_.Definition.Actions
}
I have PowerShell scripts running on Win2008 and Win2003, and found the command "schtasks" to be good enough for looking up information about scheduled tasks. This isn't a powershell command, but it's a standard function in Windows, and is compatible with Windows 2003 and 2008.
$scheduledTasks = schtasks /query /v /fo csv | ConvertFrom-Csv
#this will return an array of scheduled tasks with all info available for the task
To check if a scheduled task is ready on 2003, you'll need to make sure "Scheduled Task State" is "Enabled", and Status is blank.
On 2008 and above, Status will return enabled, disabled, running, etc.
If all you want to do is gather the basic properties of a task so you know it's name state and next run time you can use schtasks with the following methods:
function New-TaskInfo()
{
param ($TaskPath, $TaskName, $NextRunTime, $Status);
$task = new-object PSObject;
$task | add-member -type NoteProperty -Name Path-Value $TaskPath;
$task | add-member -type NoteProperty -Name Name -Value $TaskName;
$task | add-member -type NoteProperty -Name NextRunTime -Value $NextRunTime;
$task | add-member -type NoteProperty -Name Status -Value $Status;
return $task;
}
function Get-ScheduledTaskInfo
{
$tasks = #();
$queryOutput = schtasks /QUERY /FO CSV
foreach($line in $queryOutput)
{
$columns = $line.Split(',');
$taskPath = $columns[0].Replace('"', '');
if($taskPath -eq "TaskName")
{
#Ignore headder lines
continue;
}
$taskName = [System.IO.Path]::GetFileName($taskPath);
$nextRunTime = $columns[1].Replace('"', '');
$status = $columns[2].Replace('"', '');
$task = New-TaskInfo -TaskPath $taskPath -TaskName $taskName -NextRunTime $nextRunTime -Status $status;
Write-Host -ForegroundColor Green "Add Task $task";
$tasks += $task;
}
return $tasks;
}
If you then want to perform an action for a specific task you can use schtasks directly specifying data stored in the objects collected earlier.
$task = Get-ScheduledTaskInfo | Where-Object {$_.Name -eq 'TaskName'}
if($task.Status -eq 'Ready')
{
Write-Host -ForegroundColor Green "Task" $task.Name "Is" $task.Status;
#End the target task
schtasks /END /TN $task.Path;
}
You'll find all the informations you need about the missing COM object in Working with scheduled tasks from Windows PowerShell.
Using Windows Server 2012 you can use Scheduled Tasks Cmdlets in Windows PowerShell.
#ameer deen.. The code you have given returns the tasks at the root level only. Windows 2008 onwards several modifications are made to task manager and one of them is folder structure. To list all the tasks in side sub folders as well, we need to query the subfolders and iterate through them.
You can find the code sample for querying all(including the ones in subfolders) scheduled tasks at http://techibee.com/powershell/powershell-get-scheduled-tasks-list-from-windows-72008-computers/1647