top of page

View Advanced System Information on a Remote Computer

ניתן להשיג את הקוד המלא כאן

השבוע ביקרתי אצל לקוח, שהתלונן שבסביבת הSharepoint המשתמשים מרגישים איטיות.

הסביבה שעליה יושב השרת(ים) הינה סביבת SQL Cluster וגרסת SQL Server 2008 Ent.

הסביבה הינה וירטואלית. – VMware. כלומר, מספר מכונות יושבות על אותה הפלטפורמה- ESX (השרת הפיסי aka הברזלים)

בפועל VM יכול לשרת על אותו ESX(הברזל עצמו) או אחר באותה פלטפורמה – תלוי הגדרות.

בחזרה ללקוח, 2 המכונות שישבו תחת SQL Cluster.

Node1 – ישב על ESX שמכיל CPU ב 2.0Ghz

Node2 – ישב על ESX שמכיל CPU ב 3.0Ghz

Node2 היה אקטיבי במשך תקופה ארוכה, באחת מפעולות התחזוקה הוחלף הNode הפעיל לNode1. אז החלו להגיע תלונות רבות על איטיות.

לאחר זמן מה גילינו את מה שקרה.

החזרנו את הNode הפעיל לNode2 וביצענו מיגרציה(VMotion) לNode1 לESX בעל פלטפורמה זהה.

המקרה העלה חשש אצל הלקוח, שיכול להיות פוטנציאל בעייתי בעוד שרתים בארגון שמכילים SQL.

הבקשה של הלקוח הייתה למצוא דרך שאילתת T-SQL האם הCPU של הNodes זהים במקרים הבאים –

הסביבה אצל הלקוח מכילה שרתי SQL מ2005 ועד 2012 ומגוון גרסאות מערכות הפעלה Windows Server 2003 ומעלה.

קרוב לוודאי שמשימה כזאת לא הכי נכונה לביצוע מתוך SQL, אבל אהבתי את האתגר.

כמה פתרונות אפשריים עלו לי בראש:

  1. דרך SQL עצמו בעזרת – Linked Server או SQLCMD – אך במקרה של SQL Cluster הNode המשני אינו זמין.

  2. PowerShell – אולי זאת הדרך הנכונה, אבל נתקלתי בבעיה של מספר פלטפורמות. לגרסאות הwindows השונות קיים גרסת PS שונה וכך גם המטודות שונות זו מזו.

לדוגמא, הסקריפט שכתבתי יעבוד ברוב המכונות העדכניות. אך, מכונות בעלות מערכת הפעלה windows server 2003 ומטה, הקוד לא ייתמך – בגלל גרסאות שונות של PowerShell .

DECLARE @cmd VARCHAR(4000);

DECLARE @RemoteServer VARCHAR(4000) ;

DECLARE @output TABLE (line varchar(4000));

SELECT @RemoteServer = NodeName

FROM sys.dm_os_cluster_nodes

WHERE NodeName != @@servername

if @RemoteServer is null

SELECT @RemoteServer = replica_server_name

from sys.availability_replicas

where replica_server_name != @@servername

--set @RemoteServer 'print-srv'

SET @cmd = 'powershell.exe -noprofile -command "$servername = ''' + @RemoteServer + '''; invoke-command -computer $servername -scriptblock {[array]$wmiinfo = Get-WmiObject Win32_Processor; $cpu = ($wmiinfo[0].name); $cores = ( $wmiinfo | Select SocketDesignation | Measure-Object ).count; $NumberOfLogicalProcessors = ( $wmiinfo[0].NumberOfLogicalProcessors); $obj = New-Object Object; $obj | Add-Member Noteproperty Servername -value $servername; $obj | Add-Member Noteproperty CPU -value $cpu; $obj | Add-Member Noteproperty Cores -value $cores; $obj | Add-Member Noteproperty NumberOfLogicalProcessors -value $NumberOfLogicalProcessors; Write-Host ($obj | Format-List | Out-String);}"';

INSERT @output

EXEC xp_cmdshell @cmd;

IF exists(select top 1 1 from @output WHERE line like '%is not recognized as an internal or external command%')

begin

delete from @output;

SET @cmd = '%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command "$servername = ''' + @RemoteServer + '''; invoke-command -computer $servername -scriptblock {[array]$wmiinfo = Get-WmiObject Win32_Processor; $cpu = ($wmiinfo[0].name); $cores = ( $wmiinfo | Select SocketDesignation | Measure-Object ).count; $NumberOfLogicalProcessors = ( $wmiinfo[0].NumberOfLogicalProcessors); $obj = New-Object Object; $obj | Add-Member Noteproperty Servername -value $servername; $obj | Add-Member Noteproperty CPU -value $cpu; $obj | Add-Member Noteproperty Cores -value $cores; $obj | Add-Member Noteproperty NumberOfLogicalProcessors -value $NumberOfLogicalProcessors; Write-Host ($obj | Format-List | Out-String);}"';

INSERT @output

EXEC xp_cmdshell @cmd;

end

SELECT LEFT(line,CHARINDEX(' ',line)),RIGHT(line,CASE CHARINDEX(':',REVERSE(line)) WHEN 0 THEN 0 ELSE CHARINDEX(':',REVERSE(line))-1 END)

FROM @output

WHERE line IS NOT NULL;

  1. Clr – מכיוון שהשרתים מפלטפורמה 2005 – 2012, מראש עלי להכין 2 סוגי מסדי נתונים שיתמכו ב2 CLR, האחד לגרסאת .Net 2.0 לתתמוך לשרתים עד 2008R2 ו.Net 3.5 לשרתים 2012 ומעלה.

  2. ניראה לי הרפתקאה שלא כדי להיכנס בשלב זה.

  3. xp_cmdshell

פתרון יחסית קל לשימוש נין ליישם דרך הcmd

מצאתי את הכלי הבא -

כאשר אני זורק את הקבצים בשרת המתשאל(המקור) ורושם את הפקודה הבאה –

אני אוכל לדעת מה סוג הCPU של השרת אותו תשאלתי –

@" Usaage: RunRemote <\\server> Process <remote.exe> [arg1 arg2 ...]"

RunRemote \\xxx Process cmd /C wmic cpu get name

אך, הרעיון קצת יותר מורכב,

הלקוח לא מעוניין בתלות הקבצים וזיכרון המפעיל לזכור לשים את הקבצים בכל שרת.

יש מסד נתונים ניהולי והדרך היותר עדיפה היא, לשמור את הקבצים בטבלה ובהרצת פרוצדורה לזרוק אותם לנתיב מסוים – האם ניסיתם לבצע את זה פעם?

סקריפט לטעינת הקבצים-

CREATE TABLE [dbo].[Files](

[FileID] [int] IDENTITY(1,1) NOT NULL,

[FileContentType] [varchar](50) NOT NULL,

[FileName] [varchar](150) NOT NULL,

[Content] [varbinary](max) NOT NULL

) ON [PRIMARY]

insert [dbo].[Files]

Select 'dll', 'CodeProject.Remoting.Common.dll',(SELECT * FROM OPENROWSET(BULK N'D:\tmp\Release_files\CodeProject.Remoting.Common.dll', SINGLE_BLOB) AS document) document

go

insert [dbo].[Files]

Select 'dll', 'ICSharpCode.SharpZipLib.dll',(SELECT * FROM OPENROWSET(BULK N'D:\tmp\Release_files\ICSharpCode.SharpZipLib.dll', SINGLE_BLOB) AS document) document

go

insert [dbo].[Files]

Select 'exe', 'Process.exe',(SELECT * FROM OPENROWSET(BULK N'D:\tmp\Release_files\Process.exe', SINGLE_BLOB) AS document) document

go

insert [dbo].[Files]

Select 'exe', 'RunRemote.exe',(SELECT * FROM OPENROWSET(BULK N'D:\tmp\Release_files\RunRemote.exe', SINGLE_BLOB) AS document) document

go

insert [dbo].[Files]

Select 'exe', 'ServiceOnRemoteMachine.exe',(SELECT * FROM OPENROWSET(BULK N'D:\tmp\Release_files\ServiceOnRemoteMachine.exe', SINGLE_BLOB) AS document) document

go

טעינת קבצים לטבלה עברה בהצלחה.

סקריפט לשליפת הקבצים מתוך הטבלה –

דרך א - BCP

DECLARE @SQLcommand varchar(8000)

set @SQLcommand = 'bcp "SELECT Content FROM [xxx].[dbo].[Files]" queryout "D:\CodeProject.Remoting.Common.dll" -T -n -S "SQLNODE1"'

אך, התוצאה לא משביעה רצון

דרך ב – Ole Automation Procedures

sp_configure 'show advanced options', 1

GO

RECONFIGURE;

GO

sp_configure 'Ole Automation Procedures', 1

GO

RECONFIGURE;

GO

DECLARE @SQLIMG VARCHAR(MAX) ,

@DOC_PATH VARCHAR(50) = 'D:\' ,

@TIMESTAMP VARCHAR(MAX) ,

@ObjectToken INT ,

@FILENAME VARCHAR(50);

SET @TIMESTAMP = @DOC_PATH + 'CodeProject.Remoting.Common.dll';

SELECT @FILENAME = DocData

FROM [dbo].[Files];

EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT

EXEC sp_OASetProperty @ObjectToken, 'Type', 1

EXEC sp_OAMethod @ObjectToken, 'Open'

EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @FILENAME --new variable here

EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, @TIMESTAMP, 2

EXEC sp_OAMethod @ObjectToken, 'Close'

EXEC sp_OADestroy @ObjectToken

דרך ג - CLR

פה, נעזרתי בclr שניתן להשיג מכאן

כזכור לנו מהפסקה מעלה, תחילה לא רציתי להשתמש בclr בגלל קימפול ב2 סוגי .Net.

אך, יחסית פה המקרה הוא דיי פשוט.

ניתן לקחת את הקוד שמסופק מהמאמר, ולהכניס לקובץ טקסט בעל השם SqlFileProcessor.cs ומפה זה כבר דיי פשוט לקמפל לאיזה גרסה של .Net בדרך הבאה– בעזרת CMD

על מנת לקמפל את הקוד ל.Net 2.0 -

C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /target:library /out:C:\temp\SqlFileProcessor.dll C:\temp\SqlFileProcessor.cs

על מנת לקמפל את הקוד ל.Net 3.5 -

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /target:library /out:C:\temp\SqlFileProcessor.dll C:\temp\SqlFileProcessor.cs

מכאן הטעינה יחסית זהה –

USE <DB_Name>;

GO

ALTER DATABASE <DB_Name> SET TRUSTWORTHY ON;

GO

EXEC sp_changedbowner 'sa'

go

EXEC sp_configure 'clr enabled',1 ;

GO

RECONFIGURE

GO

CREATE ASSEMBLY SqlFileProcessor

from 'C:\SqlFileProcessor.dll'

WITH PERMISSION_SET =EXTERNAL_ACCESS;

GO

CREATE Function [Utility].[ufn_clr_GetBytesFromFile]( @FileName NVarchar(128)) RETURNs varbinary(max)

AS EXTERNAL NAME SqlFileProcessor.FileProcessor.GetBytesFromFile;

GO

CREATE Function [Utility].[ufn_clr_SaveFileToDisk]( @sFileName Nvarchar(128), @Blob varbinary(max))Returns int

AS EXTERNAL NAME SqlFileProcessor.FileProcessor.SaveFileToDisk

GO

הקוד לטובת התשאול המלא יראה כך –

DECLARE @RemoteServer VARCHAR(4000) ;

DECLARE @output TABLE (line varchar(4000));

DECLARE @RemoteProp TABLE([Server] sysname NOT NULL,Property VARCHAR(4000),[Value] varchar(4000));

DECLARE @cmdShell VARCHAR(4000);

DECLARE @PathcmdFile NVARCHAR(2048);

IF OBJECT_ID('tempdb..#Nodes') IS NOT NULL

DROP TABLE #Nodes;

CREATE TABLE #Nodes(NodeName sysname,[Type] sysname);

IF OBJECT_ID('tempdb..#RemoteServer') IS NOT NULL

DROP TABLE #RemoteServer;

CREATE TABLE #RemoteServer([Server] sysname NOT NULL,Property VARCHAR(4000),[Value] varchar(4000));

INSERT #Nodes

SELECT NodeName,'SQL Cluster' [Type]

FROM sys.dm_os_cluster_nodes;

if SERVERPROPERTY('IsHadrEnabled') = 1

EXEC('INSERT #Nodes

SELECT member_name,''AlwaysOn''

FROM sys.dm_hadr_cluster_members');

DECLARE @NoOutput TABLE (ID INT);

DECLARE @files TABLE (ID int IDENTITY, FileName varchar(100))

DECLARE cuNode CURSOR LOCAL FAST_FORWARD READ_ONLY FOR

SELECT NodeName

FROM #Nodes

OPEN cuNode

FETCH NEXT FROM cuNode INTO @RemoteServer

WHILE @@FETCH_STATUS = 0

BEGIN

DELETE FROM @output;

DELETE FROM @RemoteProp;

--By powershell

PRINT'By powershell'

SET @cmdShell = 'powershell.exe -noprofile -command "$servername = ''' + @RemoteServer + '''; invoke-command -computer $servername -scriptblock {[array]$wmiinfo = Get-WmiObject Win32_Processor; $cpu = ($wmiinfo[0].name); $cores = ( $wmiinfo | Select SocketDesignation | Measure-Object ).count; $NumberOfLogicalProcessors = ( $wmiinfo[0].NumberOfLogicalProcessors); $obj = New-Object Object; $obj | Add-Member Noteproperty CPU -value $cpu; $obj | Add-Member Noteproperty Cores -value $cores; $obj | Add-Member Noteproperty NumberOfLogicalProcessors -value $NumberOfLogicalProcessors; Write-Host ($obj | Format-List | Out-String);}"';

INSERT @output

EXEC master.sys.xp_cmdshell @cmdShell;

IF exists(select top 1 1 from @output WHERE line like '%is not recognized as an internal or external command%')

begin

delete from @output;

SET @cmdShell = '%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command "$servername = ''' + @RemoteServer + '''; invoke-command -computer $servername -scriptblock {[array]$wmiinfo = Get-WmiObject Win32_Processor; $cpu = ($wmiinfo[0].name); $cores = ( $wmiinfo | Select SocketDesignation | Measure-Object ).count; $NumberOfLogicalProcessors = ( $wmiinfo[0].NumberOfLogicalProcessors); $obj = New-Object Object; $obj | Add-Member Noteproperty CPU -value $cpu; $obj | Add-Member Noteproperty Cores -value $cores; $obj | Add-Member Noteproperty NumberOfLogicalProcessors -value $NumberOfLogicalProcessors; Write-Host ($obj | Format-List | Out-String);}"';

INSERT @output

EXEC master.sys.xp_cmdshell @cmdShell;

END

IF NOT EXISTS(SELECT TOP 1 1 FROM @output WHERE line = 'The system cannot find the path specified.')

INSERT @RemoteProp

SELECT @RemoteServer [Server],

LEFT(line,CHARINDEX(' ',line))[Property],

RIGHT(line,CASE CHARINDEX(':',REVERSE(line)) WHEN 0 THEN 0 ELSE CHARINDEX(':',REVERSE(line))-1 END)[Value]

FROM @output

WHERE line IS NOT NULL ;

--By cmdshell + clr

IF NOT EXISTS(SELECT TOP 1 1 FROM @RemoteProp)

BEGIN

PRINT 'By cmdshell + clr';

DELETE FROM @output;

SET @PathcmdFile = N'C:\tmp\RemoteFilesCmd\';

SET @cmdShell = 'dir "' + @PathcmdFile + '" /b';

EXEC master.dbo.xp_create_subdir @PathcmdFile;

INSERT INTO @files EXECUTE master.sys.xp_cmdshell @cmdShell;

INSERT @NoOutput

SELECT [Utility].[ufn_clr_SaveFileToDisk] (@PathcmdFile + PF.FileName ,Content)

FROM dbo.Files PF

LEFT JOIN @files DF ON DF.FileName = PF.[FileName]

WHERE DF.ID IS NULL;

SET @cmdShell = @PathcmdFile + 'RunRemote \\' + @RemoteServer + ' Process cmd /C wmic cpu get name'

DELETE FROM @output;

INSERT @output

EXEC master.sys.xp_cmdshell @cmdShell;

INSERT @RemoteProp

SELECT TOP 1 @RemoteServer [Server],'CPU' [Property],REPLACE(line,'Name ','') [Value]

FROM @output

WHERE line NOT IN ('

','Return value = 0','Completed: cmd /C wmic cpu get name','Name')

UNION ALL

SELECT TOP 1 @RemoteServer [Server],'Cores' [Property],CONVERT(VARCHAR(4000),COUNT(1)) [Value]

FROM @output

WHERE line NOT IN ('

','Return value = 0','Completed: cmd /C wmic cpu get name','Name');

END

IF EXISTS(SELECT TOP 1 1 FROM @RemoteProp)

BEGIN

SELECT Server ,

Property ,

Value

FROM @RemoteProp

END

DELETE FROM @RemoteProp;

FETCH NEXT FROM cuNode INTO @RemoteServer;

END

CLOSE cuNode;

DEALLOCATE cuNode;

SELECT *

DROP TABLE #Nodes,#RemoteServer;

ניתן להשיג את הקוד המלא כאן

Featured Posts
Check back soon
Once posts are published, you’ll see them here.
Recent Posts
Archive
Search By Tags
No tags yet.
Follow Us
  • Facebook Basic Square
  • Twitter Basic Square
  • Google+ Basic Square
bottom of page