First commit, only with CLI tools

This commit is contained in:
2019-03-13 11:06:13 +01:00
commit e88675331c
22 changed files with 1690 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
**/thumbs.bd
*.bak
api.log
client/config.ps1
BD/database*
reports/
__pycache__/
.vscode/
cooks/*.yaml

BIN
BD/emptydatabase.db Normal file

Binary file not shown.

51
README.MD Normal file
View File

@@ -0,0 +1,51 @@
# What is "Powerful Computer Manager"?
Powerful Computer Manager is the super-simple-but-powerful program to automatizate tasks in groups of computers (In a Active Directory site, or not) in a unattended way.
It inteed to be a simple SSMS alternative, that inventarizes computers and install/remove software in it, tweak system of run arbitrary commands.
You only have to write simple _cooks_ that are small yaml text files, create groups of computers and assign _cooks_ to this computers, and will be run in the destination computers
# Disclaimer
I'm using this program in the company I work with 10 computers with Windows 8.1/10 with various software (MSI,EXE,...) and tweaking services, and it works right
It's not super-stable and error proof, then, I don't have any responsability if something crashes, but you can open a Issue and will see it
# Roadmap
GUI administration in Python to superseed Powershell CLI control program and be cross-platform
# Requirements
## Server
For server you can use Linux or Windows, and in theory any environment that can run Python 3 can work as server
- Python3 with:
- Falcon
- Waitress-serve
- Json (Python3 have it preinstalled)
- Yaml
Python3 deps can be installed it using pip install command (pip install waitress falcon pyyaml)
- For controlling the program, you have to use "control.ps1" using Powershell. Powershell comes with Windows, and can be installed (PWCore) in Linux and macOS (I didn't tested it).
## Clients
- Windows:
- Windows 7 SP1 absolute minimal, but its only officially tested in 8.1 and 10, Windows 7 is being old now.
- It needs Powershell 5.0+, lower versions doesn't have some commands, not tested. In Windows 7 and 8.1, you have to update Powershell. (With Powershell 3.0/4.0, some commands like Get-Package doesn't work)
You can update from here: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell?view=powershell-6#upgrading-existing-windows-powershell
- Linux:
- Not supported yet, but when Windows be stable, I will try to do it in some way
# Instalation. Example installing Server and using a GPO of Active Directory/Task Schedule to make it work in clients
- Copy this folder to a folder in your server
- Install dependencies
- Open sysopt.py file and change values if needed
- Use bat file if you are in Windows to run the python server, or sh file if you are in Linux environment
- Go to Client folder and copy "configexample.ps1" to "config.ps1" and change their values to yours.
- For clients to work, they have to run "client\client.ps1" every 30 mins/1 hour or so, I did using Active Directory and a Task Scheduled GPO (Copying client.ps1 and config.ps1 files from Client to SysVol\DomainName\Scripts\)
- User: SYSTEM (Or NT\SYSTEM)
- Command: powershell.exe
- Args: -executionpolicy bypass -windowstyle hidden -noninteractive -nologo -file "\\SERVER\SysVol\DOMAINNAME\scripts\client.ps1"
Note: client.ps1 has to have control.ps1 file with it
- If you don't have Active Directory, you can do the Task Scheduled task in every computer without problems, and using another Shared folder, it will work

541
api.py Normal file
View File

@@ -0,0 +1,541 @@
#!/usr/bin/python3
# Build 5
##It can be run directly with "waitress-serve --port=3333 api:api"
import falcon
import random
import os
import time
import sql # SQL work file
import json,yaml
from datetime import datetime
import sysopt #File of options
def logit(text):
text = str(text)
now = datetime.now()
print ("Log: " + text)
with open('api.log', 'a') as file:
file.write("Log ("+now.strftime("%x %X") + "): ")
file.write(text)
file.write('\n')
class getComputerExists(object):
def on_get(self, request, response):
logit(request)
ComputerName,UUID = None, None
for key, value in request.params.items():
if key == "ComputerName":
ComputerName = value
if key == "UUID":
UUID = value
if ComputerName is None:
response.media = {'RESULT': '0'}
else:
result = sql.select("SELECT COUNT(*) 'RESULT' FROM COMPUTERS WHERE Name='"+ComputerName+"' AND UUID='"+UUID+"'")
if result[0]['RESULT'] == 0: # 0 or 1
if sql.select("SELECT COUNT(*) 'RESULT' FROM COMPUTERS WHERE Name='"+ComputerName+"' AND UUID IS NULL")[0]['RESULT'] != 0:
sql.insert("UPDATE COMPUTERS SET UUID='"+UUID+"' WHERE Name='"+ComputerName+"'")
response.media = {'RESULT': '1'}
return
if sysopt.addComputers == True:
sql.insert("INSERT INTO COMPUTERS (`Name`,`UUID`) VALUES('"+ComputerName+"','"+UUID+"')")
response.media = {'RESULT': '1'}
else:
response.media = {'RESULT': '0'}
else: #Exists
response.media = {'RESULT': '1'}
class getGroups(object): #Get local groups of a Computer
def on_get(self,request, response):
logit(request)
ComputerID=None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if ComputerID is None:
data = sql.select("SELECT * FROM GROUPS")
response.media = data
else:
data = sql.select("SELECT * FROM GROUPS WHERE (SELECT ID_G FROM COMPUTER_GROUP WHERE ID_C = '"+ComputerID+"')")
response.media = data
class getComputers(object):
def on_get(self,request, response):
logit(request)
ComputerID=None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if ComputerID is None:
data = sql.select("SELECT * FROM COMPUTERS")
response.media = data
else:
data = sql.select("SELECT * FROM COMPUTERS WHERE ID_C = '"+ComputerID+"')")
response.media = data
class getComputersGrp(object): #List of computers in a group
def on_get(self,request, response):
logit(request)
GroupID=None
for key, value in request.params.items():
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
data = sql.select("SELECT * FROM COMPUTERS WHERE ID_C IN (SELECT ID_C FROM COMPUTER_GROUP WHERE ID_G='"+GroupID+"') ORDER BY Name")
response.media = data
class getCook(object): # Get the list of cooks for a computer to implement (Var all=1 for see all of, implemented or not)
def on_get(self,request, response):
logit(request)
ComputerID,GroupID, SeeAll =None, None, None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if key == "SeeAll":
SeeAll = str(value)
if ComputerID is None and GroupID is None:
response.media = {'ERROR': 'I need a Group or Computer to search'}
elif ComputerID is not None and SeeAll is None:
data = sql.select("SELECT * FROM COOKS_IDG WHERE ID_G IN (SELECT ID_G FROM COMPUTER_GROUP WHERE ID_C = '"+ComputerID+"')") #All cooks for this computer
fordelete = []
for i in range(len(data)):
for key, value in data[i].items(): #Iterate in a dict (json)
if key == 'CookName':
CookName = value #Name
with open('cooks/'+CookName+'.yaml', 'r') as myfile:
filecook = myfile.read()
coun = sql.select("SELECT COUNT(*) 'RESULT' FROM COOKS_STATUS WHERE ID_C='"+ComputerID+"' AND CookName='"+CookName+"' AND Revision='"+str(yaml.safe_load(filecook)['revision'])+"' AND `Error`='0'")
if coun[0]['RESULT'] == 1:
fordelete.append(i) #Its good, do not say to client
for x in reversed(fordelete): #Deleting cooks that are implemented in client. Reverse order (Because is an array and index..)
data.pop(x)
response.media = data
else:
response.media = sql.select("SELECT * FROM COOKS_IDG WHERE ID_G IN (SELECT ID_G FROM COMPUTER_GROUP WHERE ID_C = '"+ComputerID+"')") #All cooks for this computer
class setCook(object): #Assign Cook to group
def on_get(self, request, response):
logit(request)
GroupID, CookName = None, None # Initialize
for key, value in request.params.items():
if key == "CookName":
exists = os.path.isfile('cooks/'+value+'.yaml')
CookName = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if GroupID is None or exists is False:
response.media = {'ERROR': 'GroupID is not defined or Cook not exists'}
elif int(sql.select("SELECT COUNT(*) 'COUNT' FROM COOKS_IDG WHERE `ID_G`='"+GroupID+"' AND `CookName`='"+CookName+"'")[0]['COUNT']) > 0:
response.media = {'WARNING': 'This union GROUP-CookName exists'}
else:
result = sql.insert("INSERT INTO COOKS_IDG (`ID_G`,`CookName`) VALUES ('"+GroupID+"','"+CookName+"')")
response.media = result
class delCookGrp(object): #Delete cook from a group
def on_get(self,request,response):
logit(request)
GroupID, CookName = None, None # Initialize
for key, value in request.params.items():
if key == "CookName":
exists = os.path.isfile('cooks/'+value+'.yaml')
if exists:
CookName = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if GroupID is not None and CookName is not None:
result = sql.insert("DELETE FROM COOKS_IDG WHERE ID_G='"+GroupID+"' AND CookName='"+CookName+"'")
response.media = result
else:
response.media = {'RESULT': 'Error, no Group, or CookName does\'t exists'}
class delEmptyPcsGroup(object): #Delete all computers from a group
def on_get(self,request,response):
logit(request)
GroupID = None # Initialize
for key, value in request.params.items():
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if GroupID is not None:
result = sql.insert("DELETE FROM COMPUTER_GROUP WHERE ID_G='"+GroupID+"'")
response.media = result
else:
response.media = {'RESULT': 'Error, this group doesn\'t exists'}
class getCookGrp(object): # Get cooks from a Group
def on_get(self,request,response):
logit(request)
GroupID = None # Initialize
for key, value in request.params.items():
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if GroupID is not None:
result = sql.select("SELECT * FROM COOKS_IDG WHERE ID_G='"+GroupID+"'")
response.media = result
else:
response.media = {'RESULT': 'Error, no Group selected'}
class getGrpCook(object): # Get Groups of a Cook
def on_get(self,request,response):
logit(request)
CookName = None # Initialize
for key, value in request.params.items():
if key == "CookName":
exists = os.path.isfile('cooks/'+value+'.yaml')
if exists:
CookName = value
if CookName is not None:
response.media = sql.select("SELECT Name FROM GROUPS WHERE ID_G IN (SELECT ID_G FROM COOKS_IDG WHERE CookName='"+CookName+"')")
else:
response.media = {'RESULT': 'Error, no Cook selected'}
class getStatusCook(object): # Get Status of a Cook (If Brief=1 is sent too, brief status (Completed:X, updated:X..))
def on_get(self,request,response):
logit(request)
CookName = None # Initialize
for key, value in request.params.items():
if key == "CookName":
exists = os.path.isfile('cooks/'+value+'.yaml')
if exists:
CookName = value
if CookName is not None:
response.media = sql.select("SELECT Name, Revision, Error FROM COMPUTERS,COOKS_STATUS WHERE CookName = '"+CookName+"' AND COMPUTERS.ID_C=COOKS_STATUS.ID_C")
else:
response.media = {'RESULT': 'Error, no Cook selected'}
class getLastRevisionCook(object): # Get Number Revision (Revision=X)
def on_get(self,request,response):
logit(request)
CookName = None # Initialize
for key, value in request.params.items():
if key == "CookName":
exists = os.path.isfile('cooks/'+value+'.yaml')
if exists:
CookName = value
if CookName is not None:
with open('cooks/'+CookName+'.yaml', 'r') as myfile:
filecook = myfile.read()
response.media = {'Revision': str(yaml.safe_load(filecook)['revision'])}
else:
response.media = {'RESULT': 'Error, no Cook selected'}
class addComputer(object):
def on_get(self, request, response):
logit(request)
ComputerName = None
for key,value in request.params.items():
if key == "ComputerName":
ComputerName = value
if ComputerName is None:
response.media = {'RESULT': 'Error, you need a ComputerName to add'}
else:
result = sql.insert("INSERT INTO COMPUTERS (Name) VALUES ('"+ComputerName+"')")
response.media = result
class addGroup(object):
def on_get(self, request, response):
logit(request)
GroupName= None
for key,value in request.params.items():
if key == "GroupName":
GroupName = value
if GroupName is None:
response.media = {'RESULT': 'Error, you need a GroupName to add'}
else:
result = sql.insert("INSERT INTO GROUPS (Name) VALUES ('"+GroupName+"')")
response.media = result
class delGroup(object): #Delete group
def on_get(self, request, response):
logit(request)
GroupID= None
for key,value in request.params.items():
if key == "GroupID":
GroupID = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if GroupID is None:
response.media = {'RESULT': 'Error, you need a GroupName to delete'}
else:
sql.insert("DELETE FROM COMPUTER_GROUP WHERE ID_G='"+GroupID+"'")
sql.insert("DELETE FROM COOKS_IDG WHERE ID_G='"+GroupID+"'")
result = sql.insert("DELETE FROM GROUPS WHERE ID_G='"+GroupID+"'")
response.media = result
class updGroup(object): #Delete group
def on_get(self, request, response):
logit(request)
GroupID, GroupNewName= None, None
for key,value in request.params.items():
if key == "GroupID":
GroupID = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupNewName":
Count = str(sql.select("SELECT COUNT(*) 'Count' FROM GROUPS WHERE Name='"+value+"'")[0]['Count'])
if Count == "0":
GroupNewName = value
else:
response.media = {'RESULT': 'Error, New group name exists'}
if GroupID is None or GroupNewName is None:
response.media = {'RESULT': 'Error, you need a GroupName and new name to update name'}
else:
result = sql.insert("UPDATE GROUPS SET Name='"+GroupNewName+"' WHERE ID_G='"+GroupID+"'")
response.media = result
class addGrpComputer(object): #Add computer to a group (Local)
def on_get(self, request, response):
logit(request)
ComputerID, GroupID= None, None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if ComputerID is None or GroupID is None:
response.media = {'RESULT': 'Error, you need a Name and Group to add'}
else:
result = sql.insert("INSERT INTO COMPUTER_GROUP (ID_C,ID_G) VALUES ('"+ComputerID+"','"+GroupID+"')")
response.media = result
class delGrpComputer(object): #Del computer from a group
def on_get(self, request, response):
logit(request)
ComputerID, GroupID= None, None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if key == "GroupName":
GroupID = str(sql.select("SELECT ID_G FROM GROUPS WHERE Name='"+value+"'")[0]['ID_G'])
if key == "GroupID":
GroupID = value
if ComputerID is None or GroupID is None:
response.media = {'RESULT': 'Error, you need a Name and Group to add'}
else:
result = sql.insert("DELETE FROM COMPUTER_GROUP WHERE ID_C='"+ComputerID+"' AND ID_G='"+GroupID+"'")
response.media = result
class delComputer(object): #Delete computer
def on_get(self, request, response):
logit(request)
ComputerID= None
for key, value in request.params.items():
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if ComputerID is None:
response.media = {'RESULT': 'Error, you need a Name/ComputerID to update data'}
else:
sql.insert("DELETE FROM COMPUTER_GROUP WHERE ID_C='"+ComputerID+"'")
sql.insert("DELETE FROM COOKS_STATUS WHERE ID_C='"+ComputerID+"'")
resp = sql.insert("DELETE FROM COMPUTERS WHERE ID_C='"+ComputerID+"'")
logit(resp)
class updComputer(object):
def on_get(self, request, response):
logit(request)
ComputerID, UUID= None, None
for key, value in request.params.items():
if key == "ComputerName":
try:
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
except:
response.media = {'RESULT': 'Error: Computer not exists in database'}
break
if key == "ComputerID":
ComputerID = value
if key == "UUID":
UUID = value
if ComputerID is None and response.media is None:
response.media = {'RESULT': 'Error, you need a ComputerName/ComputerID to update data'}
elif response.media is None and ComputerID is not None and UUID is not None:
Count = str(sql.select("SELECT COUNT(*) 'Count' FROM COMPUTERS WHERE UUID='"+UUID+"' AND ID_C='"+ComputerID+"'")[0]['Count'])
if Count == "0":
response.media = {'RESULT': 'Error, computer doesn\'t exists in database'}
else:
for key, value in request.params.items():
if key == "SOVersion":
response.media = sql.insert("UPDATE COMPUTERS SET SOVersion ='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "SOBit":
response.media = sql.insert("UPDATE COMPUTERS SET SOBit ='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "SOCaption":
response.media = sql.insert("UPDATE COMPUTERS SET SOCaption ='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "LastConnection":
response.media = sql.insert("UPDATE COMPUTERS SET LastConnection ='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "LastUser":
response.media = sql.insert("UPDATE COMPUTERS SET LastUser='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "RAM":
response.media = sql.insert("UPDATE COMPUTERS SET RAM='"+value+"' WHERE ID_C='"+ComputerID+"'")
if key == "CPUName":
response.media = sql.insert("UPDATE COMPUTERS SET CPUName='"+value+"' WHERE ID_C='"+ComputerID+"'")
class updCookName(object):
def on_get(self, request, response):
logit(request)
CookName, CookNewName= None, None #Initialize
for key, value in request.params.items():
if key == "CookName":
if os.path.isfile('cooks/'+value+'.yaml'):
CookName= value
else:
response.media = {'RESULT': 'Error: Cook not exists in folder'}
break
if key == "CookNewName":
if os.path.isfile('cooks/'+value+'.yaml'):
response.media = {'RESULT': 'Error: There is a cook with the new name in folder!'}
break
else:
CookNewName= value
if CookName is None or CookNewName is None:
response.media = {'RESULT': 'Error, you need the old and new Cook Name to update data'}
elif response.media is None:
old_file = os.path.join("cooks", CookName+'.yaml')
new_file = os.path.join("cooks", CookNewName+'.yaml')
os.rename(old_file, new_file)
sql.insert("UPDATE COOKS_IDG SET CookName ='"+CookNewName+"' WHERE CookName='"+CookName+"'")
response.media = sql.insert("UPDATE COOKS_STATUS SET CookName ='"+CookNewName+"' WHERE CookName='"+CookName+"'")
class loadCook(object):
def on_get(self, request, response):
logit(request)
CookName, ComputerID, UUID= None, None, None #Initialize
for key, value in request.params.items():
if key == "CookName":
if os.path.isfile('cooks/'+value+'.yaml'):
CookName= value
else:
response.media = {'RESULT': 'Error: Cook not exists in folder'}
break
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if key == "UUID":
UUID = value
if CookName is None and response.media is None:
response.media = {'RESULT': 'Error, you need a CookName to load it'}
elif response.media is None:
Count = str(sql.select("SELECT COUNT(*) 'Count' FROM COMPUTERS WHERE UUID='"+UUID+"' AND ID_C='"+ComputerID+"'")[0]['Count'])
if Count == "0":
response.media = {'RESULT': 'Error, computer doesn\'t exists in database'}
else:
with open('cooks/'+CookName+'.yaml', 'r') as myfile:
data = myfile.read()
response.media = yaml.safe_load(data)
class setCookStatus(object):
def on_get(self, request, response):
logit(request)
CookName, ComputerID, Revision, Error,ErrorDesc= None, None, None, None, ""
for key, value in request.params.items():
if key == "CookName":
if os.path.isfile('cooks/'+value+'.yaml'): # You have to know that cook exists.
CookName= value
else:
response.media = {'RESULT': 'Error: Cook not exists in folder'}
break
if key == "ComputerName":
ComputerID = str(sql.select("SELECT ID_C FROM COMPUTERS WHERE Name='"+value+"'")[0]['ID_C'])
if key == "ComputerID":
ComputerID = value
if key == "Revision":
Revision = value
if key == "Error":
Error = value
if key == "ErrorDesc":
ErrorDesc = value
if CookName is None and response.media is None:
response.media = {'RESULT': 'Error, you need a CookName to load it'}
elif response.media is None and CookName is not None and ComputerID is not None and Revision is not None and Error is not None:
statt = sql.select("SELECT COUNT(*) 'RESULT' FROM COOKS_STATUS WHERE CookName='"+CookName+"' AND ID_C='"+ComputerID+"'")[0]['RESULT']
if statt == 0:
#INSERT, NEW
response.media = sql.insert("INSERT INTO COOKS_STATUS (CookName,ID_C,Revision,`Error`,`ErrorDesc`) VALUES ('"+CookName+"', '"+ComputerID+"', '"+Revision+"','"+Error+"','"+ErrorDesc+"')")
else:
#UPDATE, NOT NEW
response.media = sql.insert("UPDATE COOKS_STATUS SET Revision='"+Revision+"',`Error`='"+Error+"',`ErrorDesc`='"+ErrorDesc+"' WHERE ID_C='"+ComputerID+"' AND CookName='"+CookName+"'")
else:
response.media = {'RESULT': 'Error in parameters...'}
logit(response.media)
api = falcon.API()
api.add_route('/get/computers', getComputers()) #Get list of computer
api.add_route('/get/computerexists', getComputerExists()) #Returns 0 or 1 (name status)
api.add_route('/get/groups', getGroups()) #Get groups of a computer (Or list if not args)
api.add_route('/get/computersgrp', getComputersGrp()) #Get computers in a group
api.add_route('/get/cook', getCook()) #Get cooks from a group
api.add_route('/set/cook', setCook()) #Assign cook to group
api.add_route('/del/cookgrp', delCookGrp()) # Deassign cook from a group
api.add_route('/del/emptypcsgroup', delEmptyPcsGroup()) # Delete all computers from a group
api.add_route('/get/cookgrp', getCookGrp()) # See cooks that have a determinated group
api.add_route('/get/grpcook', getGrpCook()) # See groups of a determinated cook
api.add_route('/get/statuscook', getStatusCook()) # See status of a cook
api.add_route('/get/lastrevisioncook', getLastRevisionCook()) # Returns number of last revision of a cook
api.add_route('/add/computer', addComputer()) #Add computer
api.add_route('/del/computer', delComputer()) #Delete computer
api.add_route('/add/group', addGroup()) #Add group to the list of local groups
api.add_route('/del/group', delGroup()) #Delete group
api.add_route('/upd/group', updGroup()) #Update group name
api.add_route('/add/grpcomputer', addGrpComputer()) #Add computer to a group
api.add_route('/del/grpcomputer', delGrpComputer()) #Delete computer from a group
api.add_route('/upd/computer', updComputer()) #Update data of computer
api.add_route('/upd/cookname', updCookName()) #Update file name of cook and SQL references to it.
api.add_route('/load/cook', loadCook()) # Load a cook (Transfer in json way)
api.add_route('/set/cookstatus', setCookStatus()) # Update status of a cook in a computer

76
client/ADcomputerData.ps1 Normal file
View File

@@ -0,0 +1,76 @@
$root = $PSCommandPath | Split-Path -Parent
. $root\config.ps1
$table = (Invoke-RestMethod -Method Get -Uri "$server/get/computers") # Get all computers
$DNSPrefix = (get-DnsClientGlobalSetting).SuffixSearchList[0] # Get DNS Prefix, needed to search computers in some environments
$array = @()
function getPrograms($computername) {
#Define the variable to hold the location of Currently Installed Programs
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
#Create an instance of the Registry Object and open the HKLM base key
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
#Drill down into the Uninstall key using the OpenSubKey Method
$regkey=$reg.OpenSubKey($UninstallKey)
#Retrieve an array of string that contain all the subkey names
$subkeys=$regkey.GetSubKeyNames()
#Open each Subkey and use GetValue Method to return the required values for each
$allprogs = @()
foreach($key in $subkeys){
$thisKey=$UninstallKey+"\\"+$key
$thisSubKey=$reg.OpenSubKey($thisKey)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$allprogs += $obj
}
return $allprogs
}
foreach ($comp in $table){
$name = -join($comp.Name,".",$DNSPrefix);
if (Test-Connection -ComputerName $name -Quiet){
Write-Output $name
$array += getPrograms($name)
}else{
Write-Output "$name Offline"
}
}
#$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | ft -auto
$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion | export-csv "$PSScriptRoot\..\Reports\software.csv" -NoTypeInformation -Delimiter ";" -Encoding ASCII
Write-Host "Guardado en ..\Reports\software.csv"
#$content = [System.IO.File]::ReadAllText("$PSScriptRoot\Reports\software.csv").Replace("A","")
#[System.IO.File]::WriteAllText("$PSScriptRoot\Reports\software.csv", $content)

310
client/PCM.fbp Normal file
View File

@@ -0,0 +1,310 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<object class="Project" expanded="1">
<property name="class_decoration">; </property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file"></property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="indent_with_spaces"></property>
<property name="internationalize">0</property>
<property name="name">MyProject1</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header"></property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Frame" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg"></property>
<property name="center">wxBOTH</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style"></property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">MainFrame</property>
<property name="pos"></property>
<property name="size">749,396</property>
<property name="style">wxDEFAULT_FRAME_STYLE</property>
<property name="subclass">; ; forward_declare</property>
<property name="title"></property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<property name="xrc_skip_sizer">1</property>
<object class="wxMenuBar" expanded="1">
<property name="bg"></property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">MyMenuBar</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">m_menubar1</property>
<property name="permission">protected</property>
<property name="pos"></property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<object class="wxMenu" expanded="1">
<property name="label">Equipos</property>
<property name="name">m_computers</property>
<property name="permission">protected</property>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Añadir equipo</property>
<property name="name">scomp_addc</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Borrar equipo</property>
<property name="name">scomp_deletec</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
</object>
<object class="wxMenu" expanded="1">
<property name="label">Grupos</property>
<property name="name">m_groups</property>
<property name="permission">protected</property>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Añadir Grupo</property>
<property name="name">sgroup_addg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Borrar grupo</property>
<property name="name">sgroup_delg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Ver equipos de un grupo</property>
<property name="name">sgroup_seeg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Vaciar grupo</property>
<property name="name">sgroup_emptyg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Cambiar nombre de un grupo</property>
<property name="name">sgroup_reng</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Recetas asignadas a un grupo</property>
<property name="name">sgroup_cooksg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
</object>
<object class="wxMenu" expanded="1">
<property name="label">Recetas</property>
<property name="name">m_cooks</property>
<property name="permission">protected</property>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Recetas de un grupo</property>
<property name="name">scook_cooksg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Añadir receta a un grupo</property>
<property name="name">scook_addcookg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Borrar receta de un grupo</property>
<property name="name">scook_deletecookg</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Renombrar receta</property>
<property name="name">scook_renamecook</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Detalles de implantación de una receta</property>
<property name="name">scook_detailscook</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
</object>
<object class="wxMenu" expanded="1">
<property name="label">Reportes</property>
<property name="name">m_reports</property>
<property name="permission">protected</property>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Reporte de equipos</property>
<property name="name">srep_rcomp</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Reporte de grupos</property>
<property name="name">srep_rgroup</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
<object class="wxMenuItem" expanded="1">
<property name="bitmap"></property>
<property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
<property name="id">wxID_ANY</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">Reporte de recetas</property>
<property name="name">srep_rcook</property>
<property name="permission">none</property>
<property name="shortcut"></property>
<property name="unchecked_bitmap"></property>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

218
client/client.ps1 Normal file
View File

@@ -0,0 +1,218 @@
# Build 2
$root = $PSCommandPath | Split-Path -Parent
. $root\config.ps1
$computerName = $env:COMPUTERNAME
$UUID=(get-wmiobject Win32_ComputerSystemProduct).UUID
$exists = Invoke-RestMethod -Method Get -Uri "$server/get/computerexists?ComputerName=$computerName&UUID=$UUID"
if ($exists.Result -eq 0){
Write-Host "Computer outside database:" $computerName
exit
}
$Timestamp = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalSeconds
# Hardware Data
$SOData = Get-CimInstance Win32_OperatingSystem
$SOVersion = $SOData.Version
if ($SOVersion -match "^10.0."){ #If its Windows 10, add revision number for knowing hotfix installed
$revi = -join(".",(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' UBR).UBR)
$SOVersion += $revi
}
# Update Computer Data
$CPUInfo = Get-WmiObject Win32_Processor
$RAMInstalled = Get-WmiObject CIM_PhysicalMemory | Measure-Object -Property capacity -Sum | ForEach-Object {[math]::round(($_.sum / 1MB),2)}
$paramInvoke = -join("$server/upd/computer?ComputerName=$computerName&UUID=",$UUID,"&SOVersion=",$SOVersion,"&SOCaption=",$SOData.Caption,"&SOBit=",$SOData.OsArchitecture.Substring(0,2),"&LastConnection=",$Timestamp,"&RAM=",$RAMInstalled,"&CPUName=",$CPUInfo.Name)
Invoke-RestMethod -Method Get -Uri $paramInvoke | out-null
# More info
$lastUser = (Get-CimInstance -ClassName Win32_ComputerSystem -Property UserName -ComputerName .).UserName
Invoke-RestMethod -Method Get -Uri "$server/upd/computer?ComputerName=$computerName&UUID=$UUID&LastUser=$lastUser" | out-null
$cooks = Invoke-RestMethod -Method Get -Uri "$server/get/cook?ComputerName=$computerName&UUID=$UUID"
foreach ($CookName in $cooks){
Set-Location $env:temp # For downloading/copying items, use system temp location
$CookName = $CookName.CookName
$cook = Invoke-RestMethod -Method Get -Uri "$server/load/cook?CookName=$CookName&ComputerName=$computerName&UUID=$UUID"
Write-Host "Receta:" $cook.name
$CookRevision = $cook.revision
$err = 0
$exit = 0
$inif = $false
$if = $true
$filesCopied = New-Object System.Collections.ArrayList # For REPOTOLOCAL, has a list of files copied to delete when finish (Do not store forever in temp)
foreach ($step in $cook.steps){
if ($err -eq 1 -and $noerror -eq 1){$err = 0; $errvar = ""} #If "noerror" is active, do not count errors
if ($err -eq 1 -or $exit -eq 1){break} # Halt if err ocurred (And noerror directive is not active)
$step = $step.Split("|")
$param = $step[1]
Write-Host $step[0] "-" $step[1]
if($inif -eq $true -and $if -eq $false){ # Only can see "ENDIF" if is in IF and is not true
if ($step[0] -ne "ENDIF" -and $step[0] -ne "ELSE"){
Write-Host $step[0] "Not executed, IF not meet"
$step[0] = "" #Disable command
$step[1] = ""
}
}
switch ($step[0].ToUpper()) { #Command
"UNINSTALL" { # Remove program
Get-Package -Name "$param*" -ErrorAction Continue #This will return error if program is not installed, do not see it.
if ($? -eq $true){ #If its True, is that package exists
Get-Package -Name "$param*" -ErrorVariable errvar | Uninstall-Package -ErrorVariable errvar
if ($? -eq $false){ # If fail then put 1 (When fail, powershell returns False)
$err = 1
}
}
}
"SERV_DISABLE" { # Disable a service
Set-Service $param -StartupType Disabled -Status Stopped -ErrorVariable errvar
if ($? -eq $false){ #If its False, it was a problem
$err = 1
}
}
"SERV_ENABLE" { # Enable a service
Set-Service $param -StartupType Automatic -Status Running -ErrorVariable errvar
if ($? -eq $false){ #If its False, it was a problem
$err = 1
}
}
"KILL_PROCESS" {
$p = Get-Process -Name "$param"
if ($? -eq $true){ # Only do something if exists
Stop-Process -InputObject $p -Force -ErrorVariable errvar -ErrorAction Continue
#if ($p.HasExited -eq $false){
# $err = 1
#}
}
}
"CMD" { # Run a cmd command. Note: Runs at high priv.
cmd.exe /c "$param"
if ($? -eq $false){ # Error in CMD
$err = 1
}
}
"PWCMD" { # Run a powershell command. Note: Runs as high priv.
Invoke-Expression $param
if ($? -eq $false){ # Error in CMD
$err = 1
}
}
"REPOTOLOCAL" { # Copy file from repo location to local ($env:temp) for use in cmd or other things
Copy-Item "$resources\$param" $env:temp -ErrorVariable errvar -Recurse -Force
if ($? -eq $false){ # Error in Copy
$err = 1
}
$filesCopied.Add($param) > $null #Add to list
}
"REMOVE" { # Remove files / folders
Remove-Item "$param" -Recurse -Force -ErrorAction Continue # They not see errors (Because error will be file not found)
}
"INSTALLMSI" { # Installs a .msi file (From $env:temp)
Start-Process msiexec.exe -Wait -ArgumentList "/norestart /quiet /I $param" -ErrorVariable errvar
if ($? -eq $false){ # Error in MSI
$err = 1
}
}
"REGFILE" { # Imports a .reg file
reg import .\$param
if ($? -eq $false){ # Error importing reg file
$err = 1
}
}
"MSG" { # Display a message
msg * "$param"
}
{$_ -in "SLEEP","PAUSE"}{ # Pause exec some seconds
[int]$secs = $param
Start-Sleep -Seconds $secs
}
"NOERROR" { #All within NOERROR doesn't generate errors and stop scripts
$noerror = 1
}
"ENDNOERROR" {
$noerror = 0
}
"IFSOFTWAREINST" { # If with software
$inif = $true #This controls IF start/stop
Get-Package -Name "$param*" -ErrorAction SilentlyContinue #This will return error if program is not installed, do not see it.
$if=$? # True -> Exists ; False -> Not exists
}
"IFSOFTWAREVER" { # If with software
$inif = $true #This controls IF start/stop
$parts = $param.Split(";")
if ($parts[1] -ne ""){ #Exists uri and filename
$p_name = $parts[0]
$p_ver = $parts[1]
Get-Package -Name "$p_name*" -MinimumVersion "$p_ver" -MaximumVersion "$p_ver" -ErrorAction SilentlyContinue #This will return error if program is not installed, do not see it.
$if=$? # True -> Exists ; False -> Not exists
}else{ #Doesn't sent right
$err = 1
$errvar = "Param not set right. Exiting..."
}
}
"IFPATHEXISTS" { # If only if a path exists (File, Folder, Registry Key..)
$inif = $true #This controls IF start/stop
Test-Path $param -PathType Any -ErrorAction SilentlyContinue
$if=$?
}
"IFPWCMD" { # If with powershell command
$inif = $true #This controls IF start/stop
Invoke-Expression $param #Executes powershell command
$if=$? # True -> Exists ; False -> Not exists
}
"ELSE" { # Turn bool $if
$if = !$if
}
"ENDIF"{ # End the if
$inif = $false
$if = $true
}
"DOWNLOAD" { #Download a file. This is a bit problematic one, will use ; to separate download and filename, and its forced to use it..
$parts = $param.Split(";")
if ($parts[1] -ne ""){ #Exists uri and filename
$progressPreference = 'silentlyContinue'
Invoke-WebRequest -Uri $parts[0] -OutFile $parts[1] -UseBasicParsing -ErrorVariable errvar
$progressPreference = 'Continue'
$filesCopied.Add($parts[1]) > $null #Add to list
}else{ #Doesn't sent right
$err = 1
$errvar = "Param not set right. Exiting..."
}
}
"EXIT"{ # Exits cook completly. If some param, exit will be with "error"
if ($param){ #Exit with error message
$noerror = 0
$err = 1
$errvar = $param
$exit = 1
}else{ #Exit as sucessful
$err = 0
$errvar = ""
$exit = 1
}
}
Default {}
}
}
# Send results
if ($errvar){ #There is an error if this has something
$errvar = $($errvar | Out-String)
$errvar = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($errvar))
$err = 1
}else{
$errvar=""
}
if ($cook.runever -eq "1"){
$err = -1 # This is for run but not for error, is by cook saw.
}
Invoke-RestMethod -Method Get -Uri "$server/set/cookstatus?ComputerName=$computerName&UUID=$UUID&CookName=$CookName&Revision=$CookRevision&Error=$err&ErrorDesc=$errvar" | out-null
#Delete files copied to temp for saving space
foreach ($element in $filesCopied) {
Remove-Item "$env:temp\$element" -ErrorAction SilentlyContinue -Recurse -Force
}
}

2
client/configexample.ps1 Normal file
View File

@@ -0,0 +1,2 @@
$server = "http://miserver.dominio:3333" #Server that runs api service
$resources = "\\MISERVER\REPOFOLDER" #Where files for copy are (Not cooks)

317
client/control.ps1 Normal file
View File

@@ -0,0 +1,317 @@
# Build 1
$root = $PSCommandPath | Split-Path -Parent
. $root\config.ps1
function seeGroups {
$answer = Invoke-RestMethod -Method Get -Uri "$server/get/groups"
foreach ($st in $answer){
Write-Host "Grupo:" $st.Name
}
}
function seeComputers {
$answer = Invoke-RestMethod -Method Get -Uri "$server/get/computers"
foreach ($st in $answer){
Write-Host "Ordenador:" $st.Name
}
}
function menuReportes {
do
{
Write-Host "================ Menu reportes ================"
Write-Host "1) Reporte de Software"
Write-Host "Q) Salir"
$input = Read-Host "Elegir una Opcion"
switch ($input)
{
'1' {
Clear-Host
. "$PSScriptRoot\ADcomputerData.ps1"
pause
} 'q' {
return
}
}
}
until ($input -eq 'q')
}
function menuEquipos {
do
{
Write-Host "================ Menu equipos ================"
Write-Host "1) Reporte de Equipos en pantalla"
Write-Host "2) Añadir equipo"
Write-Host "3) Borrar equipo"
Write-Host "4) Añadir equipo a un grupo"
Write-Host "5) Borrar equipo de un grupo"
Write-Host "Q) Salir"
$input = Read-Host "Elegir una Opcion"
switch ($input)
{
'1' {
Clear-Host
seeComputers #Function
pause
} '2' {
Clear-Host
$newHost = Read-Host -Prompt "Nombre del equipo"
if (Test-Connection -ComputerName $newHost -Quiet){
Write-Output "$newHost Online"
Write-Output "Añadiendo equipo"
Invoke-RestMethod -Method Get -Uri "$server/add/computer?ComputerName=$newHost"
}else{
Write-Output "$newHost Offline"
}
pause
} '3' {
Clear-Host
$hhost = Read-Host -Prompt "Nombre del equipo a eliminar"
Invoke-RestMethod -Method Get -Uri "$server/del/computer?ComputerName=$hhost"
} '4' {
Clear-Host
$computer = Read-Host -Prompt "Nombre del equipo"
# See all groups
seeGroups #Function
$newGroup = Read-Host -Prompt "Nombre del grupo a añadir"
Invoke-RestMethod -Method Get -Uri "$server/add/grpcomputer?ComputerName=$computer&GroupName=$newGroup"
pause
} '5' {
Clear-Host
$computer = Read-Host -Prompt "Nombre del equipo"
# See all groups
seeGroups #Function
$newGroup = Read-Host -Prompt "Nombre del grupo a quitar"
Invoke-RestMethod -Method Get -Uri "$server/add/grpcomputer?ComputerName=$computer&GroupName=$newGroup"
pause
} 'q' {
return
}
}
}
until ($input -eq 'q')
}
function menuGrupos {
do
{
Write-Host "================ Menu grupos ================"
Write-Host "1) Reporte de Grupos en pantalla"
Write-Host "2) Ver equipos de un grupo"
Write-Host "3) Añadir grupo"
Write-Host "4) Borrar grupo"
Write-Host "5) Vaciar equipos del grupo"
Write-Host "6) Cambiar nombre del grupo"
Write-Host "7) Ver recetas de un grupo"
Write-Host "Q) Salir"
$input = Read-Host "Elegir una Opción"
switch ($input)
{
'1' {
Clear-Host
seeGroups #Function
pause
} '2' {
Clear-Host
seeGroups
$GroupName = Read-Host -Prompt "Nombre del grupo a ver"
if ($GroupName){
$answer = Invoke-RestMethod -Method Get -Uri "$server/get/computersgrp?GroupName=$GroupName"
foreach ($st in $answer){
Write-Host "Ordenador:" $st.Name
}
}
pause
} '3' {
Clear-Host
$newGroup = Read-Host -Prompt "Nombre del grupo a añadir"
Invoke-RestMethod -Method Get -Uri "$server/add/group?GroupName=$newGroup"
pause
} '4' {
Clear-Host
seeGroups #Function
$grpName = Read-Host -Prompt "Nombre del grupo a eliminar"
Invoke-RestMethod -Method Get -Uri "$server/del/group?GroupName=$grpName"
} '5' {
Clear-Host
seeGroups #Function
$grpName = Read-Host -Prompt "Nombre del grupo a quitar los equipos"
Invoke-RestMethod -Method Get -Uri "$server/del/emptypcsgroup?GroupName=$grpName"
} '6' {
Clear-Host
# See all groups
seeGroups #Function
$GroupName = Read-Host -Prompt "Nombre del grupo"
$GroupNewName = Read-Host -Prompt "Nombre nuevo"
Invoke-RestMethod -Method Get -Uri "$server/upd/group?GroupName=$GroupName&GroupNewName=$GroupNewName"
pause
} '7' {
Clear-Host
# See all groups
seeGroups #Function
$GroupName = Read-Host -Prompt "Nombre del grupo para ver recetas"
if ($GroupName){
$answer = Invoke-RestMethod -Method Get -Uri "$server/get/cookgrp?GroupName=$GroupName"
foreach ($st in $answer){
Write-Host "Receta:" $st.CookName
}
}else{
Write-Host "No hay grupo"
}
pause
} 'q' {
return
}
}
}
until ($input -eq 'q')
}
function menuCooks {
do
{
Write-Host "================ Menu recetas (Cooks) ================"
Write-Host "1) Grupos de una receta"
Write-Host "2) Estado de una receta"
Write-Host "3) Detalles de cada equipo de una receta"
Write-Host "4) Añadir receta a un grupo"
Write-Host "5) Borrar receta de un grupo"
Write-Host "6) Renombrar receta"
Write-Host "Q) Salir"
$input = Read-Host "Elegir una Opción"
switch -regex ($input)
{
'1' {
Clear-Host
$CookName = Read-Host -Prompt "Nombre de la receta"
Write-Host "Grupos de la receta $CookName"
$grpcook = Invoke-RestMethod -Method Get -Uri "$server/get/grpcook?CookName=$CookName"
foreach ($st in $grpcook){
Write-Host "Grupo:" $st.Name
}
pause
} '2|3' {
Clear-Host
$CookName = Read-Host -Prompt "Nombre de la receta"
Write-Host "Grupos de la receta"
$grpcook = Invoke-RestMethod -Method Get -Uri "$server/get/grpcook?CookName=$CookName"
foreach ($st in $grpcook){
Write-Host "Grupo:" $st.Name
}
$cookrevision = Invoke-RestMethod -Method Get -Uri "$server/get/lastrevisioncook?CookName=$CookName"
$answer = Invoke-RestMethod -Method Get -Uri "$server/get/statuscook?CookName=$CookName"
if ($_ -eq '3'){
foreach ($st in $answer){
Write-Host "Ordenador:" $st.Name ", revision" $st.Revision ", error" $st.Error
}
Write-Host "------Resumen y errores------"
}
$estadistica = @{}
$estadistica.Total = 0
$estadistica.Error = 0
$estadistica.NotLast = 0
foreach ($st in $answer){
$estadistica.Total++
if ($st.Error -eq '1'){
Write-Host "Ordenador en error:" $st.Name
$estadistica.Error++
}
if ($st.Revision -ne $cookrevision.Revision){
Write-Host "Ordenador sin la última revisión de la receta:" $st.Name
$estadistica.NotLast++
}
}
Write-Host "Total implementado:" $estadistica.Total
Write-Host "Errores:" $estadistica.Error
Write-Host "Sin aplicar ultima revisión:" $estadistica.NotLast
pause
} '4' {
Clear-Host
$CookName = Read-Host -Prompt "Nombre de la receta a añadir"
seeGroups #Function
$GroupName = Read-Host -Prompt "Nombre del grupo que quieres que lo tenga"
Invoke-RestMethod -Method Get -Uri "$server/set/cook?GroupName=$GroupName&CookName=$CookName"
pause
} '5' {
Clear-Host
$CookName = Read-Host -Prompt "Nombre del receta a eliminar de un grupo"
$grpcook = Invoke-RestMethod -Method Get -Uri "$server/get/grpcook?CookName=$CookName"
foreach ($st in $grpcook){
Write-Host "Grupo:" $st.Name
}
$GroupName = Read-Host -Prompt "Nombre del grupo"
Invoke-RestMethod -Method Get -Uri "$server/del/cookgrp?GroupName=$GroupName&CookName=$CookName"
pause
} '6' {
Clear-Host
$CookName = Read-Host -Prompt "Nombre del receta actual"
$CookNewName = Read-Host -Prompt "Nuevo nombre"
Invoke-RestMethod -Method Get -Uri "$server/upd/cookname?CookNewName=$CookNewName&CookName=$CookName"
pause
} 'q' {
return
}
}
}
until ($input -eq 'q')
}
do
{
Write-Host "================ Menú principal ================"
Write-Host "1) Reportes"
Write-Host "2) Equipos"
Write-Host "3) Grupos (Locales)"
Write-Host "4) Recetas"
Write-Host "Q) Salir"
$input = Read-Host "Elegir una Opción"
switch ($input)
{
'1' {
Clear-Host
menuReportes
} '2' {
Clear-Host
menuEquipos
} '3' {
Clear-Host
menuGrupos
} '4' {
Clear-Host
menuCooks
} 'q' {
return
}
}
}
until ($input -eq 'q')

1
cooks/README.MD Normal file
View File

@@ -0,0 +1 @@
# Here you have to put cook files in .yaml

View File

@@ -0,0 +1,5 @@
name: Install exe file, example with WinSCP
revision: 1
steps:
- REPOTOLOCAL|WinSCP-5.13.4-Setup.exe
- CMD|WinSCP-5.13.4-Setup.exe /VERYSILENT /NORESTART

View File

@@ -0,0 +1,6 @@
name: Install MSI with options (Do not put standard silent options!)
revision: 2
steps:
- REPOTOLOCAL|msiwithoptions.msi
- INSTALLMSI|msiwithoptions.msi ADDLOCAL=Server SERVER_REGISTER_AS_SERVICE=1 SERVER_ADD_FIREWALL_EXCEPTION=1 SERVER_ALLOW_SAS=1
- REMOVE|C:\ProgramData\Microsoft\Windows\Start Menu\Programs\ProgramThatIDoNotWantUserToSee|Delete start menu folder

View File

@@ -0,0 +1,5 @@
name: Install MSI File. By default, it is silent
revision: 1
steps:
- REPOTOLOCAL|simplemsi.msi
- INSTALLMSI|simplemsi.msi

View File

@@ -0,0 +1,7 @@
name: Remove WinRar as Example
revision: 4
steps:
- UNINSTALL|WinRAR|This uninstalls WinRAR from Control Panel directly (Powershell powered uninstall)
- NOERROR|Do not fail if this fails
- CMD|"C:\Program Files\WinRAR\Uninstall.exe" /S
- ENDNOERROR|End of never exit if fail

24
doc/file_hierarchy.md Normal file
View File

@@ -0,0 +1,24 @@
# File hierarchy
## Root folder
- api.py -> Server file. It manages all requests from clients and connects to database
- loadserver.bat -> A helper to run api.py on Windows
- sql.py -> Helper for api.py sql sentences
- sysopt.py -> Config of api and sql
## Client folder
- client.ps1 -> This file has to run every X time (I suggest every hour) on every client and as NT\SYSTEM or local admin. You can do this with a Task Schedule as SYSTEM in a GPO (Active Directory)
- control.ps1 -> This is the cli of the program. With that you manages the client-server program. It has to be in same folder as client.ps1 file
## BD folder
- database.db -> Database file in sqlite3
## cooks folder
It has the cooks in yaml format.
Warning: Do not change name of cooks created and assigned directly.
## Doc folder
Documentation...
## REPO folder (This isn't needeed to be there)
You have to have this shared on LAN, this is the folder that cooks use to retrieve your files (As .exe/.msi) to use in scripts.

13
doc/how_works.md Normal file
View File

@@ -0,0 +1,13 @@
# Very-Fast instructions
You have to create a cook
In "Examples" you have Cook Examples
Then, you have to create a group using the CLI.
In the groups is where you assign computers and cooks
Cooks -> Group
Computers -> Group
You can have more than 1 cook in a group
You can call the groups equal to cooks and assign this cook to the group and you have a 1<->1

48
doc/howto_writecook.md Normal file
View File

@@ -0,0 +1,48 @@
# How to
Cooks are in yaml format, and have this structure.
First: Name, a line like below, with the name
"name: My Super Cook"
Then, the revision number, when this number changes, cook will be reapplied
"revision: 1"
If the cook has to run everytime client is called (And not only one time per new revision), you have to add:
"runever: 1" (1-> Everytime; 0 or no line: Run only one time)
And then, steps. First a "steps:" line, and then line by line the steps of the cook
"steps:
- PWCMD|White-Output "Hello World"
"
Steps are ever put like "Type of step" | "Command"
You can put comments in every line putting another "|" and comments at finish, like.. "CMD|WinSCP-5.13.4-Setup.exe /VERYSILENT /NORESTART|Note: Installing WinSCP"
Cook finishes executing if they had and error (Except in a NOERROR block), or if finishes right.
Note: Cooks start in directory $env:temp (%TEMP%) from SYSTEM user.
# Type of commands
- CMD and PWCMD: cmd command or Powershell command. Cmd commands can not work properly if has quotes (Because its run as cmd /c ""), if they are a Powershell equivalent, please use it. Normally cmd command uses are for install/uninstall exe programs or some like that, because powershell doesn't wait to exit a exe command
- REPOTOLOCAL: Copy a file from REPO folder configured to local in temp folder to use it. You can copy files or folders with same command.
- INSTALLMSI: Installs an MSI file silently (Params /quiet /norestart are put, do not repeat). You can add parameters (Like INSTALLMSI|superapp.msi ADDLOCAL=extension SETASDEFAULT=1)
- NOERROR and ENDNOERROR: This is for creating a block of instructions that not captures errors, then cook works ever if instruction fails
Example:
NOERROR|For deleting a file that maybe can not exist
PWCMD|Remove-Item C:\Windows\loginstall.txt
ENDNOERROR|Finish
- SERV_DISABLE and SERV_ENABLE: Enables or disables a Windows Service
- MSG|Display a message to user
- COMMENT or REM: Makes cook comments (COMMENT|This is a comment)
- KILL_PROCESS: Kill a process name if exists
- UNINSTALL: Uninstalls a program by name if its installed. It searchs it by Powershell command 'Get-Package -Name "Program*"', take care of it. You can use to see name
- REMOVE: Removes a file or folder path from local computer. It's recursive
- SLEEP and PAUSE: Pauses execution seconds specified in param
- DOWNLOAD: This downloads a file from network/internet to $env:temp/filename. Its *required* to put a filename, that goes this way: DOWNLOAD|http://www.example.org/file.zip;filename.zip. ";" is that separates URL and filename. It seems to be a limit in PowerShell Invoke-WebRequest that limits this downloads to some like 128MB
- EXIT: Terminates a cook in this instruction. If a param is specified, exit will be with error code and the param as text of error. (Ej: EXIT|Error at seeking for file)
## If types
Inside a cook, you can use IF/ELSE/ENDIF scheme.
- IFSOFTWAREINST: Runs the if, if a program is installed.
- IFSOFTWAREVER: Runs the if, if a program is installed and has X version. (Run with two param: "Name;Version")
- IFPATHEXISTS: Runs if file exists
- IFPWCMD: If powershell command returns $true (Executed succesfully), if is run
- ELSE: Else..
- ENDIF

3
doc/naming_limits.md Normal file
View File

@@ -0,0 +1,3 @@
Cooks have to be only alfanumeric caracters and "-","_" and must end with .yaml
Groups have to be alfanumeric and "-","_"

3
loadserver.bat Normal file
View File

@@ -0,0 +1,3 @@
REM Starting Server. Will only work if python and dependencies are installed and in PATH
cd /D "%~dp0"
waitress-serve --port=3333 api:api

3
loadserver.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cd "$(dirname "$(realpath "$0")")";
waitress-serve --port=3333 api:api

45
sql.py Normal file
View File

@@ -0,0 +1,45 @@
import sqlite3
import sysopt #File of options
from shutil import copyfile
from os import path
from datetime import datetime
if not path.isfile('BD/database.db'):
copyfile('BD/emptydatabase.db','BD/database.db')
def logit(text):
text = str(text)
now = datetime.now()
print ("Log: " + text)
with open('api.log', 'a') as file:
file.write("Log ("+now.strftime("%x %X") + "): ")
file.write(text)
file.write('\n')
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def select(query):
conn = sqlite3.connect('BD/database.db')
conn.row_factory = dict_factory
if sysopt.debugsql==True:
logit("SQL: "+query)
cur = conn.execute(query)
return cur.fetchall()
def insert(query):
conn = sqlite3.connect('BD/database.db')
if sysopt.debugsql==True:
logit(query)
try:
c = conn.cursor()
c.execute("SQL: "+query)
conn.commit()
conn.close()
return {'RESULT': 'OK'}
except:
return {'RESULT': 'SQLite3 Error'}

3
sysopt.py Normal file
View File

@@ -0,0 +1,3 @@
addComputers = False # False/True accept auto add computers that start client program and connect to server
debugsql = True # True is show in log all SQL queries