agent/inventory/gatherers/file/dataProvider_windows.go (198 lines of code) (raw):
//go:build windows
// +build windows
package file
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/aliyun/aliyun_assist_client/agent/util"
"github.com/aliyun/aliyun_assist_client/agent/util/osutil"
"github.com/aliyun/aliyun_assist_client/agent/util/stringutil"
"github.com/aliyun/aliyun_assist_client/common/executil"
"github.com/aliyun/aliyun_assist_client/agent/log"
"github.com/aliyun/aliyun_assist_client/agent/inventory/appconfig"
"github.com/aliyun/aliyun_assist_client/agent/inventory/model"
"github.com/google/uuid"
)
var (
startMarker = "<start" + randomString(8) + ">"
endMarker = "<end" + randomString(8) + ">"
FileInfoBatchSize = 100
fileInfoScript = `
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
function getjson($Paths){
try {
$a = Get-ItemProperty -Path $Paths -EA SilentlyContinue |
SELECT-OBJECT Name,Length,VersionInfo,@{n="LastWriteTime";e={[datetime]::ParseExact($_."LastWriteTime","MM/dd/yyyy HH:mm:ss",$null).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")}},
@{n="CreationTime";e={[datetime]::ParseExact($_."CreationTime","MM/dd/yyyy HH:mm:ss",$null).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")}},
@{n="LastAccessTime";e={[datetime]::ParseExact($_."LastAccessTime","MM/dd/yyyy HH:mm:ss",$null).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")}},DirectoryName
$jsonObj = @()
foreach ($p in $a) {
$Name = $p.Name
$Length = $p.Length
$Description = $p.VersionInfo.FileDescription
$Version = $p.VersionInfo.FileVersion
$InstalledDate = $p.CreationTime
$LastAccesstime = $p.LastAccessTime
$ProductName = $p.VersionInfo.ProductName
$ProductVersion = $p.VersionInfo.ProductVersion
$ProductLanguage = $p.VersionInfo.Language
$CompanyName = $p.VersionInfo.CompanyName
$InstalledDir = $p.DirectoryName
$ModTime = $p.LastWriteTime
$jsonObj += @"
{"CompanyName": "` + mark(`$CompanyName`) + `", "ProductName": "` + mark(`$ProductName`) + `", "ProductVersion": "$ProductVersion", "ProductLanguage": "$ProductLanguage", "Name":"$Name", "Size":"$Length",
"Description":"` + mark(`$Description`) + `" ,"FileVersion":"$Version","InstalledDate":"$InstalledDate","LastAccessTime":"$LastAccessTime","InstalledDir":"` + mark(`$InstalledDir`) + `","ModificationTime":"$ModTime"}
"@
}
$result = $jsonObj -join ","
$result = "[" + $result + "]"
[Console]::WriteLine($result)
} catch {
Write-Error $_.Exception.Message
}
}
getjson -Paths `
)
const (
PowershellCmd = "powershell"
SleepTimeMs = 5000
ScriptFileName = "getFileInfo.ps1"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func randomString(length int) string {
return uuid.New().String()[:length]
}
func mark(s string) string {
return startMarker + s + endMarker
}
var cmdExecutor = executeCommand
var writeFileText = writeFile
func executeCommand(command string, args ...string) ([]byte, error) {
return executil.Command(command, args...).CombinedOutput()
}
// expand function expands windows environment variables
func expand(s string, mapping func(string) string) (newStr string, err error) {
newStr, err = stringutil.ReplaceMarkedFields(s, "%", "%", mapping)
if err != nil {
return "", err
}
return
}
// executePowershellCommands executes commands in Powershell to get all windows files installed.
func executePowershellCommands(command, args string) (output []byte, err error) {
if output, err = cmdExecutor(PowershellCmd, command+" "+args); err != nil {
log.GetLogger().Errorf("Failed to execute command : %v %v with error - %v",
command,
args,
err.Error())
log.GetLogger().Debugf("Failed to execute command : %v %v with error - %v",
command,
args,
err.Error())
log.GetLogger().Debugf("Command Stderr: %v", string(output))
err = fmt.Errorf("Command failed with error: %v", string(output))
}
return
}
func collectDataFromPowershell(powershellCommand string, fileInfo *[]model.FileData) (err error) {
log.GetLogger().Debugf("Executing command: %v", powershellCommand)
var output []byte
var cleanOutput string
output, err = executePowershellCommands(powershellCommand, "")
if err != nil {
log.GetLogger().Errorf("Error executing command - %v", err.Error())
return
}
log.GetLogger().Debugf("Command output before clean up: %v", string(output))
cleanOutput, err = stringutil.ReplaceMarkedFields(stringutil.CleanupNewLines(string(output)), startMarker, endMarker, stringutil.CleanupJSONField)
if err != nil {
log.GetLogger().Error(err)
return
}
log.GetLogger().Debugf("Command output: %v", string(cleanOutput))
if err = json.Unmarshal([]byte(cleanOutput), fileInfo); err != nil {
err = fmt.Errorf("Unable to parse command output - %v", err.Error())
log.GetLogger().Error(err.Error())
log.GetLogger().Debugf("Error parsing command output - no data to return")
}
return
}
func writeFile(path string, commands string) (err error) {
err = osutil.WriteFile(path, commands)
return
}
// Powershell has limit on number of parameters. So execute command using script.
func createScript(commands string) (path string, err error) {
if err != nil {
log.GetLogger().Errorf("Error getting machineID")
return
}
path = filepath.Join(appconfig.DefaultDataStorePath,
util.GetInstanceId(),
appconfig.InventoryRootDirName,
appconfig.FileInventoryRootDirName,
ScriptFileName)
log.GetLogger().Debugf("Writing to script file %v", path)
err = writeFileText(path, commands)
if err != nil {
log.GetLogger().Errorf(err.Error())
}
return
}
func getPowershellCmd(paths []string) (cmd string, err error) {
var transformed []string
for _, x := range paths {
transformed = append(transformed, `"`+x+`"`)
}
cmd = fileInfoScript + strings.Join(transformed, ",")
return
}
// getMetaData creates powershell script for getting file metadata and executes the script
func getMetaDataForFiles(paths []string) (fileInfo []model.FileData, err error) {
var cmd string
cmd, err = getPowershellCmd(paths)
if err != nil {
return
}
err = collectDataFromPowershell(cmd, &fileInfo)
return
}
// Tries to create a powershell script and executes it
func createAndRunScript(paths []string) (fileInfo []model.FileData, err error) {
var cmd, path string
cmd, err = getPowershellCmd(paths)
if err != nil {
log.GetLogger().Errorf(err.Error())
return
}
path, err = createScript(cmd)
if err != nil {
log.GetLogger().Errorf(err.Error())
return
}
powershellArg := "& '" + path + "'"
log.GetLogger().Debugf("Executing command %v", powershellArg)
err = collectDataFromPowershell(powershellArg, &fileInfo)
osutil.DeleteFile(path)
return
}
// Its is more efficient to run using script. So try to run command using script.
// If there is an error we should try fallback method.
func getMetaData(paths []string) (fileInfo []model.FileData, err error) {
var batchPaths []string
var scriptErr error
fileInfo, scriptErr = createAndRunScript(paths)
// If err running the script, try fallback option
if scriptErr != nil {
for i := 0; i < len(paths); i += FileInfoBatchSize {
batchPaths = paths[i:min(i+FileInfoBatchSize, len(paths))]
fileInfoBatch, metaDataErr := getMetaDataForFiles(batchPaths)
if metaDataErr != nil {
log.GetLogger().Error(metaDataErr)
err = metaDataErr
return
}
fileInfo = append(fileInfo, fileInfoBatch...)
}
return
}
err = scriptErr
return
}